Completed
Pull Request — master (#307)
by Damian
01:45
created

AssetAdmin::getFileInsertForm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 14

Duplication

Lines 9
Ratio 39.13 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 9
loc 23
rs 9.0856
cc 2
eloc 14
nc 2
nop 1
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\AssetAdmin\Forms\AssetFormFactory;
9
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
10
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
11
use SilverStripe\Forms\DefaultFormFactory;
12
use SilverStripe\AssetAdmin\Forms\ImageFormFactory;
13
use SilverStripe\Assets\File;
14
use SilverStripe\Assets\Folder;
15
use SilverStripe\Assets\Image;
16
use SilverStripe\Assets\Storage\AssetNameGenerator;
17
use SilverStripe\Assets\Upload;
18
use SilverStripe\Control\Controller;
19
use SilverStripe\Control\HTTPRequest;
20
use SilverStripe\Control\HTTPResponse;
21
use SilverStripe\Core\Config\Config;
22
use SilverStripe\Core\Convert;
23
use SilverStripe\Core\Injector\Injector;
24
use SilverStripe\Forms\CheckboxField;
25
use SilverStripe\Forms\DateField;
26
use SilverStripe\Forms\DropdownField;
27
use SilverStripe\Forms\FieldGroup;
28
use SilverStripe\Forms\Form;
29
use SilverStripe\Forms\FormFactory;
30
use SilverStripe\Forms\HeaderField;
31
use SilverStripe\ORM\ArrayList;
32
use SilverStripe\ORM\DataList;
33
use SilverStripe\ORM\DataObject;
34
use SilverStripe\ORM\FieldType\DBHTMLText;
35
use SilverStripe\ORM\Search\SearchContext;
36
use SilverStripe\Security\Member;
37
use SilverStripe\Security\PermissionProvider;
38
use SilverStripe\Security\SecurityToken;
39
use SilverStripe\View\Requirements;
40
41
/**
42
 * AssetAdmin is the 'file store' section of the CMS.
43
 * It provides an interface for manipulating the File and Folder objects in the system.
44
 */
45
class AssetAdmin extends LeftAndMain implements PermissionProvider
46
{
47
    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...
48
49
    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...
50
51
    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...
52
53
    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...
54
55
    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...
56
        // Legacy redirect for SS3-style detail view
57
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
58
        // Pass all URLs to the index, for React to unpack
59
        'show/$FolderID/edit/$FileID' => 'index',
60
        // API access points with structured data
61
        'POST api/createFolder' => 'apiCreateFolder',
62
        'POST api/createFile' => 'apiCreateFile',
63
        'GET api/readFolder' => 'apiReadFolder',
64
        'PUT api/updateFolder' => 'apiUpdateFolder',
65
        'DELETE api/delete' => 'apiDelete',
66
        'GET api/search' => 'apiSearch',
67
    ];
68
69
    /**
70
     * Amount of results showing on a single page.
71
     *
72
     * @config
73
     * @var int
74
     */
75
    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...
76
77
    /**
78
     * @config
79
     * @see Upload->allowedMaxFileSize
80
     * @var int
81
     */
82
    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...
83
84
    /**
85
     * @var array
86
     */
87
    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...
88
        'legacyRedirectForEditView',
89
        'apiCreateFolder',
90
        'apiCreateFile',
91
        'apiReadFolder',
92
        'apiUpdateFolder',
93
        'apiDelete',
94
        'apiSearch',
95
        'FileEditForm',
96
        'AddToCampaignForm',
97
        'FileInsertForm',
98
    );
99
100
    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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
101
102
    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...
103
104
    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...
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(
118
            'delete',
119
            'SilverStripe\AssetAdmin\BatchAction\DeleteAssets',
120
            'SilverStripe\\Assets\\Folder'
121
        );
122
    }
123
124
    public function getClientConfig()
125
    {
126
        $baseLink = $this->Link();
127
        return array_merge( parent::getClientConfig(), [
128
            'reactRouter' => true,
129
            'createFileEndpoint' => [
130
                'url' => Controller::join_links($baseLink, 'api/createFile'),
131
                'method' => 'post',
132
                'payloadFormat' => 'urlencoded',
133
            ],
134
            'createFolderEndpoint' => [
135
                'url' => Controller::join_links($baseLink, 'api/createFolder'),
136
                'method' => 'post',
137
                'payloadFormat' => 'urlencoded',
138
            ],
139
            'readFolderEndpoint' => [
140
                'url' => Controller::join_links($baseLink, 'api/readFolder'),
141
                'method' => 'get',
142
                'responseFormat' => 'json',
143
            ],
144
            'searchEndpoint' => [
145
                'url' => Controller::join_links($baseLink, 'api/search'),
146
                'method' => 'get',
147
                'responseFormat' => 'json',
148
            ],
149
            'updateFolderEndpoint' => [
150
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
151
                'method' => 'put',
152
                'payloadFormat' => 'urlencoded',
153
            ],
154
            'deleteEndpoint' => [
155
                'url' => Controller::join_links($baseLink, 'api/delete'),
156
                'method' => 'delete',
157
                'payloadFormat' => 'urlencoded',
158
            ],
159
            'limit' => $this->config()->page_length,
160
            'form' => [
161
                'FileEditForm' => [
162
                    'schemaUrl' => $this->Link('schema/FileEditForm')
163
                ],
164
                'FileInsertForm' => [
165
                    'schemaUrl' => $this->Link('schema/FileInsertForm')
166
                ],
167
                'AddToCampaignForm' => [
168
                    'schemaUrl' => $this->Link('schema/AddToCampaignForm')
169
                ],
170
            ],
171
        ]);
172
    }
173
174
    /**
175
     * Fetches a collection of files by ParentID.
176
     *
177
     * @param HTTPRequest $request
178
     * @return HTTPResponse
179
     */
180
    public function apiReadFolder(HTTPRequest $request)
181
    {
182
        $params = $request->requestVars();
183
        $items = array();
184
        $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...
185
        $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...
186
187
        if (!isset($params['id']) && !strlen($params['id'])) {
188
            $this->httpError(400);
189
        }
190
191
        $folderID = (int)$params['id'];
192
        /** @var Folder $folder */
193
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
194
195
        if (!$folder) {
196
            $this->httpError(400);
197
        }
198
199
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
200
        $files = $this->getList()->filter('ParentID', $folderID);
201
202
        if ($files) {
203
            /** @var File $file */
204
            foreach ($files as $file) {
205
                if (!$file->canView()) {
206
                    continue;
207
                }
208
209
                $items[] = $this->getObjectFromData($file);
210
            }
211
        }
212
213
        // Build parents (for breadcrumbs)
214
        $parents = [];
215
        $next = $folder->Parent();
216
        while($next && $next->exists()) {
217
            array_unshift($parents, [
218
                'id' => $next->ID,
219
                'title' => $next->getTitle(),
220
                'filename' => $next->getFilename(),
221
            ]);
222
            if($next->ParentID) {
223
                $next = $next->Parent();
224
            } else {
225
                break;
226
            }
227
        }
228
229
        // Build response
230
        $response = new HTTPResponse();
231
        $response->addHeader('Content-Type', 'application/json');
232
        $response->setBody(json_encode([
233
            'files' => $items,
234
            'title' => $folder->getTitle(),
235
            'count' => count($items),
236
            'parents' => $parents,
237
            'parent' => $parents ? $parents[count($parents) - 1] : null,
238
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
239
            'folderID' => $folderID,
240
            'canEdit' => $folder->canEdit(),
241
            'canDelete' => $folder->canArchive(),
242
        ]));
243
244
        return $response;
245
    }
246
247
    /**
248
     * @param HTTPRequest $request
249
     *
250
     * @return HTTPResponse
251
     */
252
    public function apiSearch(HTTPRequest $request)
253
    {
254
        $params = $request->getVars();
255
        $list = $this->getList($params);
256
257
        $response = new HTTPResponse();
258
        $response->addHeader('Content-Type', 'application/json');
259
        $response->setBody(json_encode([
260
            // Serialisation
261
            "files" => array_map(function($file) {
262
                return $this->getObjectFromData($file);
263
            }, $list->toArray()),
264
            "count" => $list->count(),
265
        ]));
266
267
        return $response;
268
    }
269
270
    /**
271
     * @param HTTPRequest $request
272
     *
273
     * @return HTTPResponse
274
     */
275
    public function apiDelete(HTTPRequest $request)
276
    {
277
        parse_str($request->getBody(), $vars);
278
279
        // CSRF check
280
        $token = SecurityToken::inst();
281 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...
282
            return new HTTPResponse(null, 400);
283
        }
284
285 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...
286
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
287
                ->addHeader('Content-Type', 'application/json');
288
        }
289
290
        $fileIds = $vars['ids'];
291
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
292
293 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...
294
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
295
                ->addHeader('Content-Type', 'application/json');
296
        }
297
298
        if (!min(array_map(function (File $file) {
299
            return $file->canArchive();
300
        }, $files))) {
301
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
302
                ->addHeader('Content-Type', 'application/json');
303
        }
304
305
        /** @var File $file */
306
        foreach ($files as $file) {
307
            $file->doArchive();
308
        }
309
310
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
311
            ->addHeader('Content-Type', 'application/json');
312
    }
313
314
    /**
315
     * Creates a single file based on a form-urlencoded upload.
316
     *
317
     * @param HTTPRequest $request
318
     * @return HTTPRequest|HTTPResponse
319
     */
320
    public function apiCreateFile(HTTPRequest $request)
321
    {
322
        $data = $request->postVars();
323
        $upload = $this->getUpload();
324
325
        // CSRF check
326
        $token = SecurityToken::inst();
327 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...
328
            return new HTTPResponse(null, 400);
329
        }
330
331
        // Check parent record
332
        /** @var Folder $parentRecord */
333
        $parentRecord = null;
334
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
335
            $parentRecord = Folder::get()->byID($data['ParentID']);
336
        }
337
        $data['Parent'] = $parentRecord;
338
339
        $tmpFile = $request->postVar('Upload');
340
        if(!$upload->validate($tmpFile)) {
341
            $result = ['message' => null];
342
            $errors = $upload->getErrors();
343
            if ($message = array_shift($errors)) {
344
                $result['message'] = [
345
                    'type' => 'error',
346
                    'value' => $message,
347
                ];
348
            }
349
            return (new HTTPResponse(json_encode($result), 400))
350
                ->addHeader('Content-Type', 'application/json');
351
        }
352
353
        // TODO Allow batch uploads
354
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
355
        /** @var File $file */
356
        $file = Injector::inst()->create($fileClass);
357
358
        // check canCreate permissions
359 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...
360
            $result = ['message' => [
361
                'type' => 'error',
362
                'value' => _t(
363
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
364
                    'You do not have permission to add files'
365
                )
366
            ]];
367
            return (new HTTPResponse(json_encode($result), 403))
368
                ->addHeader('Content-Type', 'application/json');
369
        }
370
371
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
372 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...
373
            $result = ['message' => [
374
                'type' => 'error',
375
                'value' => _t(
376
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
377
                    'Failed to load file'
378
                )
379
            ]];
380
            return (new HTTPResponse(json_encode($result), 400))
381
                ->addHeader('Content-Type', 'application/json');
382
        }
383
384
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
385
        $file->write();
386
387
        $result = [$this->getObjectFromData($file)];
388
389
        return (new HTTPResponse(json_encode($result)))
390
            ->addHeader('Content-Type', 'application/json');
391
    }
392
393
    /**
394
     * Creates a single folder, within an optional parent folder.
395
     *
396
     * @param HTTPRequest $request
397
     * @return HTTPRequest|HTTPResponse
398
     */
399
    public function apiCreateFolder(HTTPRequest $request)
400
    {
401
        $data = $request->postVars();
402
403
        $class = 'SilverStripe\\Assets\\Folder';
404
405
        // CSRF check
406
        $token = SecurityToken::inst();
407 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...
408
            return new HTTPResponse(null, 400);
409
        }
410
411
        // check addchildren permissions
412
        /** @var Folder $parentRecord */
413
        $parentRecord = null;
414
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
415
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
416
        }
417
        $data['Parent'] = $parentRecord;
418
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
419
420
        // Build filename
421
        $baseFilename = isset($data['Name'])
422
            ? basename($data['Name'])
423
            : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NEWFOLDER', "NewFolder");
424
425
        if ($parentRecord && $parentRecord->ID) {
426
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
427
        }
428
429
        // Ensure name is unique
430
        $nameGenerator = $this->getNameGenerator($baseFilename);
431
        $filename = null;
432
        foreach ($nameGenerator as $filename) {
433
            if (! File::find($filename)) {
434
                break;
435
            }
436
        }
437
        $data['Name'] = basename($filename);
438
439
        // Create record
440
        /** @var Folder $record */
441
        $record = Injector::inst()->create($class);
442
443
        // check create permissions
444
        if (!$record->canCreate(null, $data)) {
445
            return (new HTTPResponse(null, 403))
446
                ->addHeader('Content-Type', 'application/json');
447
        }
448
449
        $record->ParentID = $data['ParentID'];
450
        $record->Name = $record->Title = basename($data['Name']);
451
        $record->write();
452
453
        $result = $this->getObjectFromData($record);
454
455
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
456
    }
457
458
    /**
459
     * Redirects 3.x style detail links to new 4.x style routing.
460
     *
461
     * @param HTTPRequest $request
462
     */
463
    public function legacyRedirectForEditView($request)
464
    {
465
        $fileID = $request->param('FileID');
466
        /** @var File $file */
467
        $file = File::get()->byID($fileID);
468
        $link = $this->getFileEditLink($file) ?: $this->Link();
469
        $this->redirect($link);
470
    }
471
472
    /**
473
     * Given a file return the CMS link to edit it
474
     *
475
     * @param File $file
476
     * @return string
477
     */
478
    public function getFileEditLink($file)
479
    {
480
        if(!$file || !$file->isInDB()) {
481
            return null;
482
        }
483
484
        return Controller::join_links(
485
            $this->Link('show'),
486
            $file->ParentID,
487
            'edit',
488
            $file->ID
489
        );
490
    }
491
492
    /**
493
     * Get the search context from {@link File}, used to create the search form
494
     * as well as power the /search API endpoint.
495
     *
496
     * @return SearchContext
497
     */
498
    public function getSearchContext()
499
    {
500
        $context = File::singleton()->getDefaultSearchContext();
501
502
        // Customize fields
503
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
504
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
505
        ->setConfig('showcalendar', true);
506
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
507
        ->setConfig('showcalendar', true);
508
        $dateGroup = FieldGroup::create(
509
            $dateHeader,
510
            $dateFrom,
511
            $dateTo
512
        );
513
        $context->addField($dateGroup);
514
        /** @skipUpgrade */
515
        $appCategories = array(
516
            'archive' => _t(
517
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryArchive',
518
                'Archive'
519
            ),
520
            'audio' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryAudio', 'Audio'),
521
            'document' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryDocument', 'Document'),
522
            'flash' => _t(
523
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryFlash',
524
                'Flash',
525
                'The fileformat'
526
            ),
527
            'image' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryImage', 'Image'),
528
            'video' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryVideo', 'Video'),
529
        );
530
        $context->addField(
531
            $typeDropdown = new DropdownField(
532
                'AppCategory',
533
                _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.Filetype', 'File type'),
534
                $appCategories
535
            )
536
        );
537
538
        $typeDropdown->setEmptyString(' ');
539
540
        $currentfolderLabel = _t(
541
            'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CurrentFolderOnly',
542
            'Limit to current folder?'
543
        );
544
        $context->addField(
545
            new CheckboxField('CurrentFolderOnly', $currentfolderLabel)
546
        );
547
        $context->getFields()->removeByName('Title');
548
549
        return $context;
550
    }
551
552
    /**
553
     * Get an asset renamer for the given filename.
554
     *
555
     * @param  string             $filename Path name
556
     * @return AssetNameGenerator
557
     */
558
    protected function getNameGenerator($filename)
559
    {
560
        return Injector::inst()
561
            ->createWithArgs('AssetNameGenerator', array($filename));
562
    }
563
564
    /**
565
     * @todo Implement on client
566
     *
567
     * @param bool $unlinked
568
     * @return ArrayList
569
     */
570
    public function breadcrumbs($unlinked = false)
571
    {
572
        return null;
573
    }
574
575
576
    /**
577
     * Don't include class namespace in auto-generated CSS class
578
     */
579
    public function baseCSSClasses()
580
    {
581
        return 'AssetAdmin LeftAndMain';
582
    }
583
584
    public function providePermissions()
585
    {
586
        return array(
587
            "CMS_ACCESS_AssetAdmin" => array(
588
                '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...
589
                    'title' => static::menu_title()
590
                )),
591
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
592
            )
593
        );
594
    }
595
596
    /**
597
     * Build a form scaffolder for this model
598
     *
599
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
600
     *
601
     * @param File $file
602
     * @return FormFactory
603
     */
604
    public function getFormFactory(File $file)
605
    {
606
        // Get service name based on file class
607
        $name = null;
0 ignored issues
show
Unused Code introduced by
$name 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...
608
        if ($file instanceof Folder) {
609
            $name = FolderFormFactory::class;
610
        } elseif ($file instanceof Image) {
611
            $name = ImageFormFactory::class;
612
        } else {
613
            $name = FileFormFactory::class;
614
        }
615
        return Injector::inst()->get($name);
616
    }
617
618
    /**
619
     * The form is used to generate a form schema,
620
     * as well as an intermediary object to process data through API endpoints.
621
     * Since it's used directly on API endpoints, it does not have any form actions.
622
     * It handles both {@link File} and {@link Folder} records.
623
     *
624
     * @param int $id
625
     * @return Form
626
     */
627
    public function getFileEditForm($id)
628
    {
629
        /** @var File $file */
630
        $file = $this->getList()->byID($id);
631
632 View Code Duplication
        if (!$file->canView()) {
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...
633
            $this->httpError(403, _t(
634
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
635
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
636
                '',
637
                ['ObjectTitle' => $file->i18n_singular_name()]
638
            ));
639
            return null;
640
        }
641
642
        $scaffolder = $this->getFormFactory($file);
643
        $form = $scaffolder->getForm($this, 'FileEditForm', [
644
            'Record' => $file
645
        ]);
646
647
        // Configure form to respond to validation errors with form schema
648
        // if requested via react.
649 View Code Duplication
        $form->setValidationResponseCallback(function() use ($form, $file) {
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...
650
            $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $file->exists() ? $file->ID : '');
651
            return $this->getSchemaResponse($form, $schemaId);
652
        });
653
654
        return $form;
655
    }
656
657
    /**
658
     * Get file edit form
659
     *
660
     * @return Form
661
     */
662 View Code Duplication
    public function FileEditForm()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
663
    {
664
        // Get ID either from posted back value, or url parameter
665
        $request = $this->getRequest();
666
        $id = $request->param('ID') ?: $request->postVar('ID');
667
        return $this->getFileEditForm($id);
668
    }
669
    
670
    /**
671
     * The form is used to generate a form schema,
672
     * as well as an intermediary object to process data through API endpoints.
673
     * Since it's used directly on API endpoints, it does not have any form actions.
674
     * It handles both {@link File} and {@link Folder} records.
675
     *
676
     * @param int $id
677
     * @return Form
678
     */
679
    public function getFileInsertForm($id)
680
    {
681
        /** @var File $file */
682
        $file = $this->getList()->byID($id);
683
        
684 View Code Duplication
        if (!$file->canView()) {
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...
685
            $this->httpError(403, _t(
686
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
687
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
688
                '',
689
                ['ObjectTitle' => $file->i18n_singular_name()]
690
            ));
691
            return null;
692
        }
693
        
694
        $scaffolder = $this->getFormFactory($file);
695
        $form = $scaffolder->getForm($this, 'FileInsertForm', [
696
            'Record' => $file,
697
            'Type' => 'insert',
698
        ]);
699
        
700
        return $form;
701
    }
702
    
703
    /**
704
     * Get file insert form
705
     *
706
     * @return Form
707
     */
708 View Code Duplication
    public function FileInsertForm()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
709
    {
710
        // Get ID either from posted back value, or url parameter
711
        $request = $this->getRequest();
712
        $id = $request->param('ID') ?: $request->postVar('ID');
713
        return $this->getFileInsertForm($id);
714
    }
715
716
    /**
717
     * @param array $data
718
     * @param Form $form
719
     * @return HTTPResponse
720
     */
721
    public function save($data, $form)
722
    {
723
        return $this->saveOrPublish($data, $form, false);
724
    }
725
726
    /**
727
     * @param array $data
728
     * @param Form $form
729
     * @return HTTPResponse
730
     */
731
    public function publish($data, $form)
732
    {
733
        return $this->saveOrPublish($data, $form, true);
734
    }
735
736
    /**
737
     * Update thisrecord
738
     *
739
     * @param array $data
740
     * @param Form $form
741
     * @param bool $doPublish
742
     * @return HTTPResponse
743
     */
744
    protected function saveOrPublish($data, $form, $doPublish = false)
745
    {
746 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...
747
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
748
                ->addHeader('Content-Type', 'application/json');
749
        }
750
751
        $id = (int) $data['ID'];
752
        /** @var File $record */
753
        $record = $this->getList()->filter('ID', $id)->first();
754
755 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...
756
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
757
                ->addHeader('Content-Type', 'application/json');
758
        }
759
760
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
761
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
762
                ->addHeader('Content-Type', 'application/json');
763
        }
764
765
        $form->saveInto($record);
766
        $record->write();
767
768
        // Publish this record and owned objects
769
        if ($doPublish) {
770
            $record->publishRecursive();
771
        }
772
773
        // Return the record data in the same response as the schema to save a postback
774
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
775
        $schemaData = $this->getSchemaForForm($this->getFileEditForm($id), $schemaId);
0 ignored issues
show
Bug introduced by
It seems like $this->getFileEditForm($id) can be null; however, getSchemaForForm() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
776
        $schemaData['record'] = $this->getObjectFromData($record);
777
        $response = new HTTPResponse(Convert::raw2json($schemaData));
778
        $response->addHeader('Content-Type', 'application/json');
779
        return $response;
780
    }
781
782
    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...
783
    {
784 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...
785
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
786
                ->addHeader('Content-Type', 'application/json');
787
        }
788
789
        $id = (int) $data['ID'];
790
        /** @var File $record */
791
        $record = $this->getList()->filter('ID', $id)->first();
792
793
        if (!$record) {
794
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
795
                ->addHeader('Content-Type', 'application/json');
796
        }
797
798
        if (!$record->canUnpublish()) {
799
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
800
                ->addHeader('Content-Type', 'application/json');
801
        }
802
803
        $record->doUnpublish();
804
805
        // Return the record data in the same response as the schema to save a postback
806
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
807
        $schemaData = $this->getSchemaForForm($this->getFileEditForm($id), $schemaId);
0 ignored issues
show
Bug introduced by
It seems like $this->getFileEditForm($id) can be null; however, getSchemaForForm() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
808
        $schemaData['record'] = $this->getObjectFromData($record);
809
        $response = new HTTPResponse(Convert::raw2json($schemaData));
810
        $response->addHeader('Content-Type', 'application/json');
811
        return $response;
812
    }
813
814
    /**
815
     * @param File $file
816
     *
817
     * @return array
818
     */
819
    protected function getObjectFromData(File $file)
820
    {
821
        $object = array(
822
            'id' => $file->ID,
823
            'created' => $file->Created,
824
            'lastUpdated' => $file->LastEdited,
825
            'owner' => null,
826
            'parent' => null,
827
            'title' => $file->Title,
828
            'exists' => $file->exists(), // Broken file check
829
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
830
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
831
            'name' => $file->Name,
832
            'filename' => $file->Filename,
833
            '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...
834
            'size' => $file->Size,
835
            'url' => $file->AbsoluteURL,
836
            'published' => $file->isPublished(),
837
            'modified' => $file->isModifiedOnDraft(),
838
            'draft' => $file->isOnDraftOnly(),
839
            'canEdit' => $file->canEdit(),
840
            'canDelete' => $file->canArchive(),
841
        );
842
843
        /** @var Member $owner */
844
        $owner = $file->Owner();
845
846
        if ($owner) {
847
            $object['owner'] = array(
848
                'id' => $owner->ID,
849
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
850
            );
851
        }
852
853
        /** @var Folder $parent */
854
        $parent = $file->Parent();
855
856
        if ($parent) {
857
            $object['parent'] = array(
858
                'id' => $parent->ID,
859
                'title' => $parent->Title,
860
                'filename' => $parent->Filename,
861
            );
862
        }
863
864
        /** @var File $file */
865
        if ($file->getIsImage()) {
866
            $width = (int)Config::inst()->get(self::class, 'thumbnail_width');
867
            $height = (int)Config::inst()->get(self::class, 'thumbnail_height');
868
869
            $thumbnail = $file->FitMax($width, $height);
870
            if ($thumbnail && $thumbnail->exists()) {
871
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
872
            }
873
            $object['dimensions']['width'] = $file->Width;
874
            $object['dimensions']['height'] = $file->Height;
0 ignored issues
show
Bug introduced by
The property Height does not seem to exist. Did you mean asset_preview_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...
875
        }
876
877
        return $object;
878
    }
879
880
    /**
881
     * Returns the files and subfolders contained in the currently selected folder,
882
     * defaulting to the root node. Doubles as search results, if any search parameters
883
     * are set through {@link SearchForm()}.
884
     *
885
     * @param array $params Unsanitised request parameters
886
     * @return DataList
887
     */
888
    protected function getList($params = array())
889
    {
890
        $context = $this->getSearchContext();
891
892
        // Overwrite name filter to search both Name and Title attributes
893
        $context->removeFilterByName('Name');
894
895
        // Lazy loaded list. Allows adding new filters through SearchContext.
896
        /** @var DataList $list */
897
        $list = $context->getResults($params);
898
899
        // Re-add previously removed "Name" filter as combined filter
900
        // TODO Replace with composite SearchFilter once that API exists
901
        if(!empty($params['Name'])) {
902
            $list = $list->filterAny(array(
903
                'Name:PartialMatch' => $params['Name'],
904
                'Title:PartialMatch' => $params['Name']
905
            ));
906
        }
907
908
        // Optionally limit search to a folder (non-recursive)
909
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
910
            $list = $list->filter('ParentID', $params['ParentID']);
911
        }
912
913
        // Date filtering
914 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...
915
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
916
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
917
        }
918 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...
919
            $toDate = new DateField(null, null, $params['CreatedTo']);
920
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
921
        }
922
923
        // Categories
924
        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...
925
            $extensions = File::config()->app_categories[$filters['AppCategory']];
926
            $list = $list->filter('Name:PartialMatch', $extensions);
927
        }
928
929
        // Sort folders first
930
        $list = $list->sort(
931
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
932
        );
933
934
        // Pagination
935
        if (isset($filters['page']) && isset($filters['limit'])) {
936
            $page = $filters['page'];
937
            $limit = $filters['limit'];
938
            $offset = ($page - 1) * $limit;
939
            $list = $list->limit($limit, $offset);
940
        }
941
942
        // Access checks
943
        $list = $list->filterByCallback(function(File $file) {
944
            return $file->canView();
945
        });
946
947
        return $list;
948
    }
949
950
    /**
951
     * Action handler for adding pages to a campaign
952
     *
953
     * @param array $data
954
     * @param Form $form
955
     * @return DBHTMLText|HTTPResponse
956
     */
957
    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...
958
    {
959
        $id = $data['ID'];
960
        $record = $this->getList()->byID($id);
961
962
        $handler = AddToCampaignHandler::create($this, $record);
963
        $results = $handler->addToCampaign($record, $data['Campaign']);
964
        if (!is_null($results)) {
965
            $request = $this->getRequest();
966
            if($request->getHeader('X-Formschema-Request')) {
967
                $data = $this->getSchemaForForm($handler->Form($record));
968
                $data['message'] = $results;
969
970
                $response = new HTTPResponse(Convert::raw2json($data));
971
                $response->addHeader('Content-Type', 'application/json');
972
                return $response;
973
            }
974
            return $results;
975
        }
976
    }
977
978
    /**
979
     * Url handler for add to campaign form
980
     *
981
     * @param HTTPRequest $request
982
     * @return Form
983
     */
984
    public function AddToCampaignForm($request)
985
    {
986
        // Get ID either from posted back value, or url parameter
987
        $id = $request->param('ID') ?: $request->postVar('ID');
988
        return $this->getAddToCampaignForm($id);
989
    }
990
991
    /**
992
     * @param int $id
993
     * @return Form
994
     */
995
    public function getAddToCampaignForm($id)
996
    {
997
        // Get record-specific fields
998
        $record = $this->getList()->byID($id);
999
1000
        if (!$record) {
1001
            $this->httpError(404, _t(
1002
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1003
                'That {Type} couldn\'t be found',
1004
                '',
1005
                ['Type' => File::singleton()->i18n_singular_name()]
1006
            ));
1007
            return null;
1008
        }
1009 View Code Duplication
        if (!$record->canView()) {
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...
1010
            $this->httpError(403, _t(
1011
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1012
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1013
                '',
1014
                ['ObjectTitle' => $record->i18n_singular_name()]
1015
            ));
1016
            return null;
1017
        }
1018
1019
        $handler = AddToCampaignHandler::create($this, $record);
1020
        $form = $handler->Form($record);
1021
        
1022 View Code Duplication
        $form->setValidationResponseCallback(function() use ($form, $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...
1023
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1024
            return $this->getSchemaResponse($form, $schemaId);
1025
        });
1026
    
1027
        return $form;
1028
    }
1029
1030
    /**
1031
     * @return Upload
1032
     */
1033
    protected function getUpload()
1034
    {
1035
        $upload = Upload::create();
1036
        $upload->getValidator()->setAllowedExtensions(
1037
            // filter out '' since this would be a regex problem on JS end
1038
            array_filter(File::config()->get('allowed_extensions'))
1039
        );
1040
1041
        return $upload;
1042
    }
1043
}
1044