Completed
Pull Request — master (#323)
by Damian
01:43
created

AssetAdmin::getFormFactory()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 3
eloc 9
nc 3
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\UploadField;
10
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
11
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
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\Convert;
22
use SilverStripe\Core\Injector\Injector;
23
use SilverStripe\Forms\CheckboxField;
24
use SilverStripe\Forms\DateField;
25
use SilverStripe\Forms\DropdownField;
26
use SilverStripe\Forms\FieldGroup;
27
use SilverStripe\Forms\Form;
28
use SilverStripe\Forms\FormFactory;
29
use SilverStripe\Forms\HeaderField;
30
use SilverStripe\ORM\ArrayList;
31
use SilverStripe\ORM\DataList;
32
use SilverStripe\ORM\DataObject;
33
use SilverStripe\ORM\FieldType\DBHTMLText;
34
use SilverStripe\ORM\Search\SearchContext;
35
use SilverStripe\Security\Member;
36
use SilverStripe\Security\PermissionProvider;
37
use SilverStripe\Security\SecurityToken;
38
use SilverStripe\View\Requirements;
39
40
/**
41
 * AssetAdmin is the 'file store' section of the CMS.
42
 * It provides an interface for manipulating the File and Folder objects in the system.
43
 */
44
class AssetAdmin extends LeftAndMain implements PermissionProvider
45
{
46
    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...
47
48
    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...
49
50
    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...
51
52
    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...
53
54
    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...
55
        // Legacy redirect for SS3-style detail view
56
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
57
        // Pass all URLs to the index, for React to unpack
58
        'show/$FolderID/edit/$FileID' => 'index',
59
        // API access points with structured data
60
        'POST api/createFolder' => 'apiCreateFolder',
61
        'POST api/createFile' => 'apiCreateFile',
62
        'GET api/readFolder' => 'apiReadFolder',
63
        'PUT api/updateFolder' => 'apiUpdateFolder',
64
        'DELETE api/delete' => 'apiDelete',
65
        'GET api/search' => 'apiSearch',
66
    ];
67
68
    /**
69
     * Amount of results showing on a single page.
70
     *
71
     * @config
72
     * @var int
73
     */
74
    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...
75
76
    /**
77
     * @config
78
     * @see Upload->allowedMaxFileSize
79
     * @var int
80
     */
81
    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...
82
83
    /**
84
     * @var array
85
     */
86
    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...
87
        'legacyRedirectForEditView',
88
        'apiCreateFolder',
89
        'apiCreateFile',
90
        'apiReadFolder',
91
        'apiUpdateFolder',
92
        'apiDelete',
93
        'apiSearch',
94
        'FileEditForm',
95
        'AddToCampaignForm',
96
        'FileInsertForm',
97
        'FileSelectForm',
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
                'FileSelectForm' => [
168
                    'schemaUrl' => $this->Link('schema/FileSelectForm')
169
                ],
170
                'AddToCampaignForm' => [
171
                    'schemaUrl' => $this->Link('schema/AddToCampaignForm')
172
                ],
173
            ],
174
        ]);
175
    }
176
177
    /**
178
     * Fetches a collection of files by ParentID.
179
     *
180
     * @param HTTPRequest $request
181
     * @return HTTPResponse
182
     */
183
    public function apiReadFolder(HTTPRequest $request)
184
    {
185
        $params = $request->requestVars();
186
        $items = array();
187
        $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...
188
        $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...
189
190
        if (!isset($params['id']) && !strlen($params['id'])) {
191
            $this->httpError(400);
192
        }
193
194
        $folderID = (int)$params['id'];
195
        /** @var Folder $folder */
196
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
197
198
        if (!$folder) {
199
            $this->httpError(400);
200
        }
201
202
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
203
        $files = $this->getList()->filter('ParentID', $folderID);
204
205
        if ($files) {
206
            /** @var File $file */
207
            foreach ($files as $file) {
208
                if (!$file->canView()) {
209
                    continue;
210
                }
211
212
                $items[] = $this->getObjectFromData($file);
213
            }
214
        }
215
216
        // Build parents (for breadcrumbs)
217
        $parents = [];
218
        $next = $folder->Parent();
219
        while($next && $next->exists()) {
220
            array_unshift($parents, [
221
                'id' => $next->ID,
222
                'title' => $next->getTitle(),
223
                'filename' => $next->getFilename(),
224
            ]);
225
            if($next->ParentID) {
226
                $next = $next->Parent();
227
            } else {
228
                break;
229
            }
230
        }
231
232
        // Build response
233
        $response = new HTTPResponse();
234
        $response->addHeader('Content-Type', 'application/json');
235
        $response->setBody(json_encode([
236
            'files' => $items,
237
            'title' => $folder->getTitle(),
238
            'count' => count($items),
239
            'parents' => $parents,
240
            'parent' => $parents ? $parents[count($parents) - 1] : null,
241
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
242
            'folderID' => $folderID,
243
            'canEdit' => $folder->canEdit(),
244
            'canDelete' => $folder->canArchive(),
245
        ]));
246
247
        return $response;
248
    }
249
250
    /**
251
     * @param HTTPRequest $request
252
     *
253
     * @return HTTPResponse
254
     */
255
    public function apiSearch(HTTPRequest $request)
256
    {
257
        $params = $request->getVars();
258
        $list = $this->getList($params);
259
260
        $response = new HTTPResponse();
261
        $response->addHeader('Content-Type', 'application/json');
262
        $response->setBody(json_encode([
263
            // Serialisation
264
            "files" => array_map(function($file) {
265
                return $this->getObjectFromData($file);
266
            }, $list->toArray()),
267
            "count" => $list->count(),
268
        ]));
269
270
        return $response;
271
    }
272
273
    /**
274
     * @param HTTPRequest $request
275
     *
276
     * @return HTTPResponse
277
     */
278
    public function apiDelete(HTTPRequest $request)
279
    {
280
        parse_str($request->getBody(), $vars);
281
282
        // CSRF check
283
        $token = SecurityToken::inst();
284 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...
285
            return new HTTPResponse(null, 400);
286
        }
287
288 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...
289
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
290
                ->addHeader('Content-Type', 'application/json');
291
        }
292
293
        $fileIds = $vars['ids'];
294
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
295
296 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...
297
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
298
                ->addHeader('Content-Type', 'application/json');
299
        }
300
301
        if (!min(array_map(function (File $file) {
302
            return $file->canArchive();
303
        }, $files))) {
304
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
305
                ->addHeader('Content-Type', 'application/json');
306
        }
307
308
        /** @var File $file */
309
        foreach ($files as $file) {
310
            $file->doArchive();
311
        }
312
313
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
314
            ->addHeader('Content-Type', 'application/json');
315
    }
316
317
    /**
318
     * Creates a single file based on a form-urlencoded upload.
319
     *
320
     * @param HTTPRequest $request
321
     * @return HTTPRequest|HTTPResponse
322
     */
323
    public function apiCreateFile(HTTPRequest $request)
324
    {
325
        $data = $request->postVars();
326
        $upload = $this->getUpload();
327
328
        // CSRF check
329
        $token = SecurityToken::inst();
330 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...
331
            return new HTTPResponse(null, 400);
332
        }
333
334
        // Check parent record
335
        /** @var Folder $parentRecord */
336
        $parentRecord = null;
337
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
338
            $parentRecord = Folder::get()->byID($data['ParentID']);
339
        }
340
        $data['Parent'] = $parentRecord;
341
342
        $tmpFile = $request->postVar('Upload');
343
        if(!$upload->validate($tmpFile)) {
344
            $result = ['message' => null];
345
            $errors = $upload->getErrors();
346
            if ($message = array_shift($errors)) {
347
                $result['message'] = [
348
                    'type' => 'error',
349
                    'value' => $message,
350
                ];
351
            }
352
            return (new HTTPResponse(json_encode($result), 400))
353
                ->addHeader('Content-Type', 'application/json');
354
        }
355
356
        // TODO Allow batch uploads
357
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
358
        /** @var File $file */
359
        $file = Injector::inst()->create($fileClass);
360
361
        // check canCreate permissions
362 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...
363
            $result = ['message' => [
364
                'type' => 'error',
365
                'value' => _t(
366
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
367
                    'You do not have permission to add files'
368
                )
369
            ]];
370
            return (new HTTPResponse(json_encode($result), 403))
371
                ->addHeader('Content-Type', 'application/json');
372
        }
373
374
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
375 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...
376
            $result = ['message' => [
377
                'type' => 'error',
378
                'value' => _t(
379
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
380
                    'Failed to load file'
381
                )
382
            ]];
383
            return (new HTTPResponse(json_encode($result), 400))
384
                ->addHeader('Content-Type', 'application/json');
385
        }
386
387
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
388
        $file->write();
389
390
        $result = [$this->getObjectFromData($file)];
391
392
        return (new HTTPResponse(json_encode($result)))
393
            ->addHeader('Content-Type', 'application/json');
394
    }
395
396
    /**
397
     * Creates a single folder, within an optional parent folder.
398
     *
399
     * @param HTTPRequest $request
400
     * @return HTTPRequest|HTTPResponse
401
     */
402
    public function apiCreateFolder(HTTPRequest $request)
403
    {
404
        $data = $request->postVars();
405
406
        $class = 'SilverStripe\\Assets\\Folder';
407
408
        // CSRF check
409
        $token = SecurityToken::inst();
410 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...
411
            return new HTTPResponse(null, 400);
412
        }
413
414
        // check addchildren permissions
415
        /** @var Folder $parentRecord */
416
        $parentRecord = null;
417
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
418
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
419
        }
420
        $data['Parent'] = $parentRecord;
421
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
422
423
        // Build filename
424
        $baseFilename = isset($data['Name'])
425
            ? basename($data['Name'])
426
            : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NEWFOLDER', "NewFolder");
427
428
        if ($parentRecord && $parentRecord->ID) {
429
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
430
        }
431
432
        // Ensure name is unique
433
        $nameGenerator = $this->getNameGenerator($baseFilename);
434
        $filename = null;
435
        foreach ($nameGenerator as $filename) {
436
            if (! File::find($filename)) {
437
                break;
438
            }
439
        }
440
        $data['Name'] = basename($filename);
441
442
        // Create record
443
        /** @var Folder $record */
444
        $record = Injector::inst()->create($class);
445
446
        // check create permissions
447
        if (!$record->canCreate(null, $data)) {
448
            return (new HTTPResponse(null, 403))
449
                ->addHeader('Content-Type', 'application/json');
450
        }
451
452
        $record->ParentID = $data['ParentID'];
453
        $record->Name = $record->Title = basename($data['Name']);
454
        $record->write();
455
456
        $result = $this->getObjectFromData($record);
457
458
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
459
    }
460
461
    /**
462
     * Redirects 3.x style detail links to new 4.x style routing.
463
     *
464
     * @param HTTPRequest $request
465
     */
466
    public function legacyRedirectForEditView($request)
467
    {
468
        $fileID = $request->param('FileID');
469
        /** @var File $file */
470
        $file = File::get()->byID($fileID);
471
        $link = $this->getFileEditLink($file) ?: $this->Link();
472
        $this->redirect($link);
473
    }
474
475
    /**
476
     * Given a file return the CMS link to edit it
477
     *
478
     * @param File $file
479
     * @return string
480
     */
481
    public function getFileEditLink($file)
482
    {
483
        if(!$file || !$file->isInDB()) {
484
            return null;
485
        }
486
487
        return Controller::join_links(
488
            $this->Link('show'),
489
            $file->ParentID,
490
            'edit',
491
            $file->ID
492
        );
493
    }
494
495
    /**
496
     * Get the search context from {@link File}, used to create the search form
497
     * as well as power the /search API endpoint.
498
     *
499
     * @return SearchContext
500
     */
501
    public function getSearchContext()
502
    {
503
        $context = File::singleton()->getDefaultSearchContext();
504
505
        // Customize fields
506
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
507
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
508
        ->setConfig('showcalendar', true);
509
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
510
        ->setConfig('showcalendar', true);
511
        $dateGroup = FieldGroup::create(
512
            $dateHeader,
513
            $dateFrom,
514
            $dateTo
515
        );
516
        $context->addField($dateGroup);
517
        /** @skipUpgrade */
518
        $appCategories = array(
519
            'archive' => _t(
520
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryArchive',
521
                'Archive'
522
            ),
523
            'audio' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryAudio', 'Audio'),
524
            'document' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryDocument', 'Document'),
525
            'flash' => _t(
526
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryFlash',
527
                'Flash',
528
                'The fileformat'
529
            ),
530
            'image' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryImage', 'Image'),
531
            'video' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryVideo', 'Video'),
532
        );
533
        $context->addField(
534
            $typeDropdown = new DropdownField(
535
                'AppCategory',
536
                _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.Filetype', 'File type'),
537
                $appCategories
538
            )
539
        );
540
541
        $typeDropdown->setEmptyString(' ');
542
543
        $currentfolderLabel = _t(
544
            'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CurrentFolderOnly',
545
            'Limit to current folder?'
546
        );
547
        $context->addField(
548
            new CheckboxField('CurrentFolderOnly', $currentfolderLabel)
549
        );
550
        $context->getFields()->removeByName('Title');
551
552
        return $context;
553
    }
554
555
    /**
556
     * Get an asset renamer for the given filename.
557
     *
558
     * @param  string             $filename Path name
559
     * @return AssetNameGenerator
560
     */
561
    protected function getNameGenerator($filename)
562
    {
563
        return Injector::inst()
564
            ->createWithArgs('AssetNameGenerator', array($filename));
565
    }
566
567
    /**
568
     * @todo Implement on client
569
     *
570
     * @param bool $unlinked
571
     * @return ArrayList
572
     */
573
    public function breadcrumbs($unlinked = false)
574
    {
575
        return null;
576
    }
577
578
579
    /**
580
     * Don't include class namespace in auto-generated CSS class
581
     */
582
    public function baseCSSClasses()
583
    {
584
        return 'AssetAdmin LeftAndMain';
585
    }
586
587
    public function providePermissions()
588
    {
589
        return array(
590
            "CMS_ACCESS_AssetAdmin" => array(
591
                '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...
592
                    'title' => static::menu_title()
593
                )),
594
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
595
            )
596
        );
597
    }
598
599
    /**
600
     * Build a form scaffolder for this model
601
     *
602
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
603
     *
604
     * @param File $file
605
     * @return FormFactory
606
     */
607
    public function getFormFactory(File $file)
608
    {
609
        // Get service name based on file class
610
        $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...
611
        if ($file instanceof Folder) {
612
            $name = FolderFormFactory::class;
613
        } elseif ($file instanceof Image) {
614
            $name = ImageFormFactory::class;
615
        } else {
616
            $name = FileFormFactory::class;
617
        }
618
        return Injector::inst()->get($name);
619
    }
620
621
    /**
622
     * The form is used to generate a form schema,
623
     * as well as an intermediary object to process data through API endpoints.
624
     * Since it's used directly on API endpoints, it does not have any form actions.
625
     * It handles both {@link File} and {@link Folder} records.
626
     *
627
     * @param int $id
628
     * @return Form
629
     */
630
    public function getFileEditForm($id)
631
    {
632
        return $this->getAbstractFileForm($id, 'FileEditForm');
633
    }
634
635
    /**
636
     * Get file edit form
637
     *
638
     * @return Form
639
     */
640 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...
641
    {
642
        // Get ID either from posted back value, or url parameter
643
        $request = $this->getRequest();
644
        $id = $request->param('ID') ?: $request->postVar('ID');
645
        return $this->getFileEditForm($id);
646
    }
647
648
    /**
649
     * The form is used to generate a form schema,
650
     * as well as an intermediary object to process data through API endpoints.
651
     * Since it's used directly on API endpoints, it does not have any form actions.
652
     * It handles both {@link File} and {@link Folder} records.
653
     *
654
     * @param int $id
655
     * @return Form
656
     */
657
    public function getFileInsertForm($id)
658
    {
659
        return $this->getAbstractFileForm($id, 'FileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT ]);
660
    }
661
662
    /**
663
     * Get file insert form
664
     *
665
     * @return Form
666
     */
667 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...
668
    {
669
        // Get ID either from posted back value, or url parameter
670
        $request = $this->getRequest();
671
        $id = $request->param('ID') ?: $request->postVar('ID');
672
        return $this->getFileInsertForm($id);
673
    }
674
675
    /**
676
     * Abstract method for generating a form for a file
677
     *
678
     * @param int $id Record ID
679
     * @param string $name Form name
680
     * @param array $context Form context
681
     * @return Form
682
     */
683
    protected function getAbstractFileForm($id, $name, $context = [])
684
    {
685
        /** @var File $file */
686
        $file = $this->getList()->byID($id);
687
688 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...
689
            $this->httpError(403, _t(
690
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
691
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
692
                '',
693
                ['ObjectTitle' => $file->i18n_singular_name()]
694
            ));
695
            return null;
696
        }
697
698
        // Pass to form factory
699
        $augmentedContext = array_merge($context, ['Record' => $file]);
700
        $scaffolder = $this->getFormFactory($file);
701
        $form = $scaffolder->getForm($this, $name, $augmentedContext);
702
703
        // Configure form to respond to validation errors with form schema
704
        // if requested via react.
705
        $form->setValidationResponseCallback(function() use ($form, $file, $name) {
706
            $schemaId = Controller::join_links(
707
                $this->Link('schema'),
708
                $name,
709
                $file->isInDB() ? $file->ID : ''
710
            );
711
            return $this->getSchemaResponse($form, $schemaId);
712
        });
713
714
        return $form;
715
    }
716
717 View Code Duplication
    public function FileSelectForm()
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...
718
    {
719
        // Get ID either from posted back value, or url parameter
720
        $request = $this->getRequest();
721
        $id = $request->param('ID') ?: $request->postVar('ID');
722
        return $this->getFileSelectForm($id);
723
    }
724
725
    public function getFileSelectForm($id) {
726
        return $this->getAbstractFileForm($id, 'FileSelectForm', [ 'Type' => AssetFormFactory::TYPE_SELECT ]);
727
    }
728
729
    /**
730
     * @param array $data
731
     * @param Form $form
732
     * @return HTTPResponse
733
     */
734
    public function save($data, $form)
735
    {
736
        return $this->saveOrPublish($data, $form, false);
737
    }
738
739
    /**
740
     * @param array $data
741
     * @param Form $form
742
     * @return HTTPResponse
743
     */
744
    public function publish($data, $form)
745
    {
746
        return $this->saveOrPublish($data, $form, true);
747
    }
748
749
    /**
750
     * Update thisrecord
751
     *
752
     * @param array $data
753
     * @param Form $form
754
     * @param bool $doPublish
755
     * @return HTTPResponse
756
     */
757
    protected function saveOrPublish($data, $form, $doPublish = false)
758
    {
759 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...
760
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
761
                ->addHeader('Content-Type', 'application/json');
762
        }
763
764
        $id = (int) $data['ID'];
765
        /** @var File $record */
766
        $record = $this->getList()->filter('ID', $id)->first();
767
768 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...
769
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
770
                ->addHeader('Content-Type', 'application/json');
771
        }
772
773
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
774
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
775
                ->addHeader('Content-Type', 'application/json');
776
        }
777
778
        $form->saveInto($record);
779
        $record->write();
780
781
        // Publish this record and owned objects
782
        if ($doPublish) {
783
            $record->publishRecursive();
784
        }
785
786
        // Return the record data in the same response as the schema to save a postback
787
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
788
        $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...
789
        $schemaData['record'] = $this->getObjectFromData($record);
790
        $response = new HTTPResponse(Convert::raw2json($schemaData));
791
        $response->addHeader('Content-Type', 'application/json');
792
        return $response;
793
    }
794
795
    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...
796
    {
797 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...
798
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
799
                ->addHeader('Content-Type', 'application/json');
800
        }
801
802
        $id = (int) $data['ID'];
803
        /** @var File $record */
804
        $record = $this->getList()->filter('ID', $id)->first();
805
806
        if (!$record) {
807
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
808
                ->addHeader('Content-Type', 'application/json');
809
        }
810
811
        if (!$record->canUnpublish()) {
812
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
813
                ->addHeader('Content-Type', 'application/json');
814
        }
815
816
        $record->doUnpublish();
817
818
        // Return the record data in the same response as the schema to save a postback
819
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
820
        $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...
821
        $schemaData['record'] = $this->getObjectFromData($record);
822
        $response = new HTTPResponse(Convert::raw2json($schemaData));
823
        $response->addHeader('Content-Type', 'application/json');
824
        return $response;
825
    }
826
827
    /**
828
     * @param File $file
829
     *
830
     * @return array
831
     */
832
    public function getObjectFromData(File $file)
833
    {
834
        $object = array(
835
            'id' => $file->ID,
836
            'created' => $file->Created,
837
            'lastUpdated' => $file->LastEdited,
838
            'owner' => null,
839
            'parent' => null,
840
            'title' => $file->Title,
841
            'exists' => $file->exists(), // Broken file check
842
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
843
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
844
            'name' => $file->Name,
845
            'filename' => $file->Filename,
846
            '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...
847
            'size' => $file->Size,
848
            'url' => $file->AbsoluteURL,
849
            'published' => $file->isPublished(),
850
            'modified' => $file->isModifiedOnDraft(),
851
            'draft' => $file->isOnDraftOnly(),
852
            'canEdit' => $file->canEdit(),
853
            'canDelete' => $file->canArchive(),
854
        );
855
856
        /** @var Member $owner */
857
        $owner = $file->Owner();
858
859
        if ($owner) {
860
            $object['owner'] = array(
861
                'id' => $owner->ID,
862
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
863
            );
864
        }
865
866
        /** @var Folder $parent */
867
        $parent = $file->Parent();
868
869
        if ($parent) {
870
            $object['parent'] = array(
871
                'id' => $parent->ID,
872
                'title' => $parent->Title,
873
                'filename' => $parent->Filename,
874
            );
875
        }
876
877
        /** @var File $file */
878
        if ($file->getIsImage()) {
879
            // Small thumbnail
880
            $smallWidth = UploadField::config()->get('thumbnail_width');
881
            $smallHeight = UploadField::config()->get('thumbnail_height');
882
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
883
            if ($smallThumbnail && $smallThumbnail->exists()) {
884
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
885
            }
886
887
            // Large thumbnail
888
            $width = $this->config()->get('thumbnail_width');
889
            $height = $this->config()->get('thumbnail_height');
890
            $thumbnail = $file->FitMax($width, $height);
891
            if ($thumbnail && $thumbnail->exists()) {
892
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
893
            }
894
            $object['dimensions']['width'] = $file->Width;
895
            $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...
896
        }
897
898
        return $object;
899
    }
900
901
    /**
902
     * Returns the files and subfolders contained in the currently selected folder,
903
     * defaulting to the root node. Doubles as search results, if any search parameters
904
     * are set through {@link SearchForm()}.
905
     *
906
     * @param array $params Unsanitised request parameters
907
     * @return DataList
908
     */
909
    protected function getList($params = array())
910
    {
911
        $context = $this->getSearchContext();
912
913
        // Overwrite name filter to search both Name and Title attributes
914
        $context->removeFilterByName('Name');
915
916
        // Lazy loaded list. Allows adding new filters through SearchContext.
917
        /** @var DataList $list */
918
        $list = $context->getResults($params);
919
920
        // Re-add previously removed "Name" filter as combined filter
921
        // TODO Replace with composite SearchFilter once that API exists
922
        if(!empty($params['Name'])) {
923
            $list = $list->filterAny(array(
924
                'Name:PartialMatch' => $params['Name'],
925
                'Title:PartialMatch' => $params['Name']
926
            ));
927
        }
928
929
        // Optionally limit search to a folder (non-recursive)
930
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
931
            $list = $list->filter('ParentID', $params['ParentID']);
932
        }
933
934
        // Date filtering
935 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...
936
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
937
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
938
        }
939 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...
940
            $toDate = new DateField(null, null, $params['CreatedTo']);
941
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
942
        }
943
944
        // Categories
945
        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...
946
            $extensions = File::config()->app_categories[$filters['AppCategory']];
947
            $list = $list->filter('Name:PartialMatch', $extensions);
948
        }
949
950
        // Sort folders first
951
        $list = $list->sort(
952
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
953
        );
954
955
        // Pagination
956
        if (isset($filters['page']) && isset($filters['limit'])) {
957
            $page = $filters['page'];
958
            $limit = $filters['limit'];
959
            $offset = ($page - 1) * $limit;
960
            $list = $list->limit($limit, $offset);
961
        }
962
963
        // Access checks
964
        $list = $list->filterByCallback(function(File $file) {
965
            return $file->canView();
966
        });
967
968
        return $list;
969
    }
970
971
    /**
972
     * Action handler for adding pages to a campaign
973
     *
974
     * @param array $data
975
     * @param Form $form
976
     * @return DBHTMLText|HTTPResponse
977
     */
978
    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...
979
    {
980
        $id = $data['ID'];
981
        $record = $this->getList()->byID($id);
982
983
        $handler = AddToCampaignHandler::create($this, $record);
984
        $results = $handler->addToCampaign($record, $data['Campaign']);
985
        if (!isset($results)) {
986
            return null;
987
        }
988
        $request = $this->getRequest();
989
        if($request->getHeader('X-Formschema-Request')) {
990
            $data = $this->getSchemaForForm($handler->Form($record));
991
            $data['message'] = $results;
992
993
            $response = new HTTPResponse(Convert::raw2json($data));
994
            $response->addHeader('Content-Type', 'application/json');
995
            return $response;
996
        }
997
        return $results;
998
    }
999
1000
    /**
1001
     * Url handler for add to campaign form
1002
     *
1003
     * @param HTTPRequest $request
1004
     * @return Form
1005
     */
1006
    public function AddToCampaignForm($request)
1007
    {
1008
        // Get ID either from posted back value, or url parameter
1009
        $id = $request->param('ID') ?: $request->postVar('ID');
1010
        return $this->getAddToCampaignForm($id);
1011
    }
1012
1013
    /**
1014
     * @param int $id
1015
     * @return Form
1016
     */
1017
    public function getAddToCampaignForm($id)
1018
    {
1019
        // Get record-specific fields
1020
        $record = $this->getList()->byID($id);
1021
1022
        if (!$record) {
1023
            $this->httpError(404, _t(
1024
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1025
                'That {Type} couldn\'t be found',
1026
                '',
1027
                ['Type' => File::singleton()->i18n_singular_name()]
1028
            ));
1029
            return null;
1030
        }
1031 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...
1032
            $this->httpError(403, _t(
1033
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1034
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1035
                '',
1036
                ['ObjectTitle' => $record->i18n_singular_name()]
1037
            ));
1038
            return null;
1039
        }
1040
1041
        $handler = AddToCampaignHandler::create($this, $record);
1042
        $form = $handler->Form($record);
1043
1044
        $form->setValidationResponseCallback(function() use ($form, $id) {
1045
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1046
            return $this->getSchemaResponse($form, $schemaId);
1047
        });
1048
1049
        return $form;
1050
    }
1051
1052
    /**
1053
     * @return Upload
1054
     */
1055
    protected function getUpload()
1056
    {
1057
        $upload = Upload::create();
1058
        $upload->getValidator()->setAllowedExtensions(
1059
            // filter out '' since this would be a regex problem on JS end
1060
            array_filter(File::config()->get('allowed_extensions'))
1061
        );
1062
1063
        return $upload;
1064
    }
1065
}
1066