Completed
Push — master ( 84ae23...c3e3e3 )
by Ingo
02:19
created

AssetAdmin::saveOrPublish()   C

Complexity

Conditions 10
Paths 9

Size

Total Lines 47
Code Lines 24

Duplication

Lines 4
Ratio 8.51 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 4
loc 47
rs 5.1578
cc 10
eloc 24
nc 9
nop 3

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\AssetAdmin\Controller;
4
5
use InvalidArgumentException;
6
use SilverStripe\Admin\AddToCampaignHandler;
7
use SilverStripe\Admin\CMSBatchActionHandler;
8
use SilverStripe\Admin\LeftAndMain;
9
use SilverStripe\AssetAdmin\BatchAction\DeleteAssets;
10
use SilverStripe\AssetAdmin\Forms\AssetFormFactory;
11
use SilverStripe\AssetAdmin\Forms\UploadField;
12
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
13
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
14
use SilverStripe\AssetAdmin\Forms\FileHistoryFormFactory;
15
use SilverStripe\AssetAdmin\Forms\ImageFormFactory;
16
use SilverStripe\Assets\File;
17
use SilverStripe\Assets\Folder;
18
use SilverStripe\Assets\Image;
19
use SilverStripe\Assets\Storage\AssetNameGenerator;
20
use SilverStripe\Assets\Upload;
21
use SilverStripe\Control\Controller;
22
use SilverStripe\Control\HTTPRequest;
23
use SilverStripe\Control\HTTPResponse;
24
use SilverStripe\Core\Injector\Injector;
25
use SilverStripe\Forms\CheckboxField;
26
use SilverStripe\Forms\DateField;
27
use SilverStripe\Forms\DropdownField;
28
use SilverStripe\Forms\FieldGroup;
29
use SilverStripe\Forms\Form;
30
use SilverStripe\Forms\FormFactory;
31
use SilverStripe\Forms\HeaderField;
32
use SilverStripe\ORM\ArrayList;
33
use SilverStripe\ORM\DataList;
34
use SilverStripe\ORM\DataObject;
35
use SilverStripe\ORM\FieldType\DBHTMLText;
36
use SilverStripe\ORM\Search\SearchContext;
37
use SilverStripe\ORM\ValidationResult;
38
use SilverStripe\Security\Member;
39
use SilverStripe\Security\PermissionProvider;
40
use SilverStripe\Security\SecurityToken;
41
use SilverStripe\View\Requirements;
42
use SilverStripe\ORM\Versioning\Versioned;
43
use Exception;
44
45
/**
46
 * AssetAdmin is the 'file store' section of the CMS.
47
 * It provides an interface for manipulating the File and Folder objects in the system.
48
 */
49
class AssetAdmin extends LeftAndMain implements PermissionProvider
50
{
51
    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...
52
53
    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...
54
55
    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...
56
57
    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...
58
59
    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...
60
        // Legacy redirect for SS3-style detail view
61
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
62
        // Pass all URLs to the index, for React to unpack
63
        'show/$FolderID/edit/$FileID' => 'index',
64
        // API access points with structured data
65
        'POST api/createFile' => 'apiCreateFile',
66
        'POST api/uploadFile' => 'apiUploadFile',
67
        'GET api/history' => 'apiHistory'
68
    ];
69
70
    /**
71
     * Amount of results showing on a single page.
72
     *
73
     * @config
74
     * @var int
75
     */
76
    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...
77
78
    /**
79
     * @config
80
     * @see Upload->allowedMaxFileSize
81
     * @var int
82
     */
83
    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...
84
85
    /**
86
     * @config
87
     *
88
     * @var int
89
     */
90
    private static $max_history_entries = 100;
0 ignored issues
show
Unused Code introduced by
The property $max_history_entries 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...
91
92
    /**
93
     * @var array
94
     */
95
    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...
96
        'legacyRedirectForEditView',
97
        'apiCreateFile',
98
        'apiUploadFile',
99
        'apiHistory',
100
        'fileEditForm',
101
        'fileHistoryForm',
102
        'addToCampaignForm',
103
        'fileInsertForm',
104
        'schema',
105
        'fileSelectForm',
106
    );
107
108
    private static $required_permission_codes = 'CMS_ACCESS_AssetAdmin';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $required_permission_codes is not used and could be removed.

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

Loading history...
109
110
    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...
111
112
    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...
113
114
    /**
115
     * Set up the controller
116
     */
117
    public function init()
118
    {
119
        parent::init();
120
121
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
122
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
123
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
124
125
        CMSBatchActionHandler::register('delete', DeleteAssets::class, Folder::class);
126
    }
127
128
    public function getClientConfig()
129
    {
130
        $baseLink = $this->Link();
131
        return array_merge(parent::getClientConfig(), [
132
            'reactRouter' => true,
133
            'createFileEndpoint' => [
134
                'url' => Controller::join_links($baseLink, 'api/createFile'),
135
                'method' => 'post',
136
                'payloadFormat' => 'urlencoded',
137
            ],
138
            'uploadFileEndpoint' => [
139
                'url' => Controller::join_links($baseLink, 'api/uploadFile'),
140
                'method' => 'post',
141
                'payloadFormat' => 'urlencoded',
142
            ],
143
            'historyEndpoint' => [
144
                'url' => Controller::join_links($baseLink, 'api/history'),
145
                'method' => 'get',
146
                'responseFormat' => 'json',
147
            ],
148
            'limit' => $this->config()->page_length,
149
            'form' => [
150
                'fileEditForm' => [
151
                    'schemaUrl' => $this->Link('schema/fileEditForm')
152
                ],
153
                'fileInsertForm' => [
154
                    'schemaUrl' => $this->Link('schema/fileInsertForm')
155
                ],
156
                'fileSelectForm' => [
157
                    'schemaUrl' => $this->Link('schema/fileSelectForm')
158
                ],
159
                'addToCampaignForm' => [
160
                    'schemaUrl' => $this->Link('schema/addToCampaignForm')
161
                ],
162
                'fileHistoryForm' => [
163
                    'schemaUrl' => $this->Link('schema/fileHistoryForm')
164
                ]
165
            ],
166
        ]);
167
    }
168
169
    /**
170
     * Creates a single file based on a form-urlencoded upload.
171
     *
172
     * @param HTTPRequest $request
173
     * @return HTTPRequest|HTTPResponse
174
     */
175
    public function apiCreateFile(HTTPRequest $request)
176
    {
177
        $data = $request->postVars();
178
        $upload = $this->getUpload();
179
180
        // CSRF check
181
        $token = SecurityToken::inst();
182 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...
183
            return new HTTPResponse(null, 400);
184
        }
185
186
        // Check parent record
187
        /** @var Folder $parentRecord */
188
        $parentRecord = null;
189 View Code Duplication
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
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...
190
            $parentRecord = Folder::get()->byID($data['ParentID']);
191
        }
192
        $data['Parent'] = $parentRecord;
193
194
        $tmpFile = $request->postVar('Upload');
195 View Code Duplication
        if (!$upload->validate($tmpFile)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
196
            $result = ['message' => null];
197
            $errors = $upload->getErrors();
198
            if ($message = array_shift($errors)) {
199
                $result['message'] = [
200
                    'type' => 'error',
201
                    'value' => $message,
202
                ];
203
            }
204
            return (new HTTPResponse(json_encode($result), 400))
205
                ->addHeader('Content-Type', 'application/json');
206
        }
207
208
        // TODO Allow batch uploads
209
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
210
        /** @var File $file */
211
        $file = Injector::inst()->create($fileClass);
212
213
        // check canCreate permissions
214 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...
215
            $result = ['message' => [
216
                'type' => 'error',
217
                'value' => _t(
218
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
219
                    'You do not have permission to add files'
220
                )
221
            ]];
222
            return (new HTTPResponse(json_encode($result), 403))
223
                ->addHeader('Content-Type', 'application/json');
224
        }
225
226
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
227 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...
228
            $result = ['message' => [
229
                'type' => 'error',
230
                'value' => _t(
231
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
232
                    'Failed to load file'
233
                )
234
            ]];
235
            return (new HTTPResponse(json_encode($result), 400))
236
                ->addHeader('Content-Type', 'application/json');
237
        }
238
239
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
240
        $file->write();
241
242
        $result = [$this->getObjectFromData($file)];
243
244
        return (new HTTPResponse(json_encode($result)))
245
            ->addHeader('Content-Type', 'application/json');
246
    }
247
248
    /**
249
     * Upload a new asset for a pre-existing record. Returns the asset tuple.
250
     *
251
     * @param HTTPRequest $request
252
     * @return HTTPRequest|HTTPResponse
253
     */
254
    public function apiUploadFile(HTTPRequest $request)
255
    {
256
        $data = $request->postVars();
257
        $upload = $this->getUpload();
258
259
        // CSRF check
260
        $token = SecurityToken::inst();
261 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...
262
            return new HTTPResponse(null, 400);
263
        }
264
265
        // Check parent record
266
        /** @var Folder $parentRecord */
267
        $parentRecord = null;
268 View Code Duplication
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
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...
269
            $parentRecord = Folder::get()->byID($data['ParentID']);
270
        }
271
272
        $tmpFile = $data['Upload'];
273 View Code Duplication
        if (!$upload->validate($tmpFile)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
274
            $result = ['message' => null];
275
            $errors = $upload->getErrors();
276
            if ($message = array_shift($errors)) {
277
                $result['message'] = [
278
                    'type' => 'error',
279
                    'value' => $message,
280
                ];
281
            }
282
            return (new HTTPResponse(json_encode($result), 400))
283
                ->addHeader('Content-Type', 'application/json');
284
        }
285
286
        $folder = $parentRecord ? $parentRecord->getFilename() : '/';
287
288
        try {
289
            $tuple = $upload->load($tmpFile, $folder);
290
        } catch (Exception $e) {
291
            $result = [
292
                'message' => [
293
                    'type' => 'error',
294
                    'value' => $e->getMessage(),
295
                ]
296
            ];
297
            return (new HTTPResponse(json_encode($result), 400))
298
                ->addHeader('Content-Type', 'application/json');
299
        }
300
301
        if ($upload->isError()) {
302
            $result['message'] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
303
                'type' => 'error',
304
                'value' => implode(' ' . PHP_EOL, $upload->getErrors()),
305
            ];
306
            return (new HTTPResponse(json_encode($result), 400))
307
                ->addHeader('Content-Type', 'application/json');
308
        }
309
310
        $tuple['Name'] = basename($tuple['Filename']);
311
        return (new HTTPResponse(json_encode($tuple)))
312
            ->addHeader('Content-Type', 'application/json');
313
    }
314
315
    /**
316
     * Returns a JSON array for history of a given file ID. Returns a list of all the history.
317
     *
318
     * @param HTTPRequest $request
319
     * @return HTTPResponse
320
     */
321
    public function apiHistory(HTTPRequest $request)
322
    {
323
        // CSRF check not required as the GET request has no side effects.
324
        $fileId = $request->getVar('fileId');
325
326
        if (!$fileId || !is_numeric($fileId)) {
327
            return new HTTPResponse(null, 400);
328
        }
329
330
        $class = File::class;
331
        $file = DataObject::get($class)->byID($fileId);
332
333
        if (!$file) {
334
            return new HTTPResponse(null, 404);
335
        }
336
337
        if (!$file->canView()) {
338
            return new HTTPResponse(null, 403);
339
        }
340
341
        $versions = Versioned::get_all_versions($class, $fileId)
342
            ->limit($this->config()->max_history_entries)
343
            ->sort('Version', 'DESC');
344
345
        $output = array();
346
        $next = array();
347
        $prev = null;
348
349
        // swap the order so we can get the version number to compare against.
350
        // i.e version 3 needs to know version 2 is the previous version
351
        $copy = $versions->map('Version', 'Version')->toArray();
352
        foreach (array_reverse($copy) as $k => $v) {
353
            if ($prev) {
354
                $next[$v] = $prev;
355
            }
356
357
            $prev = $v;
358
        }
359
360
        $_cachedMembers = array();
361
362
        /** @var File $version */
363
        foreach ($versions as $version) {
364
            $author = null;
365
366
            if ($version->AuthorID) {
367
                if (!isset($_cachedMembers[$version->AuthorID])) {
368
                    $_cachedMembers[$version->AuthorID] = DataObject::get(Member::class)
369
                        ->byID($version->AuthorID);
370
                }
371
372
                $author = $_cachedMembers[$version->AuthorID];
373
            }
374
375
            if ($version->canView()) {
376
                if (isset($next[$version->Version])) {
377
                    $summary = $version->humanizedChanges(
378
                        $version->Version,
379
                        $next[$version->Version]
380
                    );
381
382
                    // if no summary returned by humanizedChanges, i.e we cannot work out what changed, just show a
383
                    // generic message
384
                    if (!$summary) {
385
                        $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.SAVEDFILE', "Saved file");
386
                    }
387
                } else {
388
                    $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UPLOADEDFILE', "Uploaded file");
389
                }
390
391
                $output[] = array(
392
                    'versionid' => $version->Version,
393
                    'date_ago' => $version->dbObject('LastEdited')->Ago(),
394
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
395
                    'status' => ($version->WasPublished) ? _t('File.PUBLISHED', 'Published') : '',
396
                    'author' => ($author)
397
                        ? $author->Name
398
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UNKNOWN', "Unknown"),
399
                    'summary' => ($summary)
400
                        ? $summary
401
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NOSUMMARY', "No summary available")
402
                );
403
            }
404
        }
405
406
        return
407
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
408
    }
409
410
    /**
411
     * Redirects 3.x style detail links to new 4.x style routing.
412
     *
413
     * @param HTTPRequest $request
414
     */
415
    public function legacyRedirectForEditView($request)
416
    {
417
        $fileID = $request->param('FileID');
418
        /** @var File $file */
419
        $file = File::get()->byID($fileID);
420
        $link = $this->getFileEditLink($file) ?: $this->Link();
421
        $this->redirect($link);
422
    }
423
424
    /**
425
     * Given a file return the CMS link to edit it
426
     *
427
     * @param File $file
428
     * @return string
429
     */
430
    public function getFileEditLink($file)
431
    {
432
        if (!$file || !$file->isInDB()) {
433
            return null;
434
        }
435
436
        return Controller::join_links(
437
            $this->Link('show'),
438
            $file->ParentID,
439
            'edit',
440
            $file->ID
441
        );
442
    }
443
444
    /**
445
     * Get the search context from {@link File}, used to create the search form
446
     * as well as power the /search API endpoint.
447
     *
448
     * @return SearchContext
449
     */
450
    public function getSearchContext()
451
    {
452
        $context = File::singleton()->getDefaultSearchContext();
453
454
        // Customize fields
455
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
456
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
457
        ->setConfig('showcalendar', true);
458
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
459
        ->setConfig('showcalendar', true);
460
        $dateGroup = FieldGroup::create(
461
            $dateHeader,
462
            $dateFrom,
463
            $dateTo
464
        );
465
        $context->addField($dateGroup);
466
        /** @skipUpgrade */
467
        $appCategories = array(
468
            'archive' => _t(
469
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryArchive',
470
                'Archive'
471
            ),
472
            'audio' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryAudio', 'Audio'),
473
            'document' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryDocument', 'Document'),
474
            'flash' => _t(
475
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryFlash',
476
                'Flash',
477
                'The fileformat'
478
            ),
479
            'image' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryImage', 'Image'),
480
            'video' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryVideo', 'Video'),
481
        );
482
        $context->addField(
483
            $typeDropdown = new DropdownField(
484
                'AppCategory',
485
                _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.Filetype', 'File type'),
486
                $appCategories
487
            )
488
        );
489
490
        $typeDropdown->setEmptyString(' ');
491
492
        $currentfolderLabel = _t(
493
            'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CurrentFolderOnly',
494
            'Limit to current folder?'
495
        );
496
        $context->addField(
497
            new CheckboxField('CurrentFolderOnly', $currentfolderLabel)
498
        );
499
        $context->getFields()->removeByName('Title');
500
501
        return $context;
502
    }
503
504
    /**
505
     * Get an asset renamer for the given filename.
506
     *
507
     * @param  string             $filename Path name
508
     * @return AssetNameGenerator
509
     */
510
    protected function getNameGenerator($filename)
511
    {
512
        return Injector::inst()
513
            ->createWithArgs('AssetNameGenerator', array($filename));
514
    }
515
516
    /**
517
     * @todo Implement on client
518
     *
519
     * @param bool $unlinked
520
     * @return ArrayList
521
     */
522
    public function breadcrumbs($unlinked = false)
523
    {
524
        return null;
525
    }
526
527
528
    /**
529
     * Don't include class namespace in auto-generated CSS class
530
     */
531
    public function baseCSSClasses()
532
    {
533
        return 'AssetAdmin LeftAndMain';
534
    }
535
536
    public function providePermissions()
537
    {
538
        return array(
539
            "CMS_ACCESS_AssetAdmin" => array(
540
                '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...
541
                    'title' => static::menu_title()
542
                )),
543
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
544
            )
545
        );
546
    }
547
548
    /**
549
     * Build a form scaffolder for this model
550
     *
551
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
552
     *
553
     * @param File $file
554
     * @return FormFactory
555
     */
556
    public function getFormFactory(File $file)
557
    {
558
        // Get service name based on file class
559
        $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...
560
        if ($file instanceof Folder) {
561
            $name = FolderFormFactory::class;
562
        } elseif ($file instanceof Image) {
563
            $name = ImageFormFactory::class;
564
        } else {
565
            $name = FileFormFactory::class;
566
        }
567
        return Injector::inst()->get($name);
568
    }
569
570
    /**
571
     * The form is used to generate a form schema,
572
     * as well as an intermediary object to process data through API endpoints.
573
     * Since it's used directly on API endpoints, it does not have any form actions.
574
     * It handles both {@link File} and {@link Folder} records.
575
     *
576
     * @param int $id
577
     * @return Form
578
     */
579
    public function getFileEditForm($id)
580
    {
581
        return $this->getAbstractFileForm($id, 'fileEditForm');
582
    }
583
584
    /**
585
     * Get file edit form
586
     *
587
     * @return Form
588
     */
589 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...
590
    {
591
        // Get ID either from posted back value, or url parameter
592
        $request = $this->getRequest();
593
        $id = $request->param('ID') ?: $request->postVar('ID');
594
        return $this->getFileEditForm($id);
595
    }
596
597
    /**
598
     * The form is used to generate a form schema,
599
     * as well as an intermediary object to process data through API endpoints.
600
     * Since it's used directly on API endpoints, it does not have any form actions.
601
     * It handles both {@link File} and {@link Folder} records.
602
     *
603
     * @param int $id
604
     * @return Form
605
     */
606
    public function getFileInsertForm($id)
607
    {
608
        return $this->getAbstractFileForm($id, 'fileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT ]);
609
    }
610
611
    /**
612
     * Get file insert form
613
     *
614
     * @return Form
615
     */
616 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...
617
    {
618
        // Get ID either from posted back value, or url parameter
619
        $request = $this->getRequest();
620
        $id = $request->param('ID') ?: $request->postVar('ID');
621
        return $this->getFileInsertForm($id);
622
    }
623
624
    /**
625
     * Abstract method for generating a form for a file
626
     *
627
     * @param int $id Record ID
628
     * @param string $name Form name
629
     * @param array $context Form context
630
     * @return Form
631
     */
632
    protected function getAbstractFileForm($id, $name, $context = [])
633
    {
634
        /** @var File $file */
635
        $file = $this->getList()->byID($id);
636
637 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...
638
            $this->httpError(403, _t(
639
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
640
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
641
                '',
642
                ['ObjectTitle' => $file->i18n_singular_name()]
643
            ));
644
            return null;
645
        }
646
647
        // Pass to form factory
648
        $augmentedContext = array_merge($context, ['Record' => $file]);
649
        $scaffolder = $this->getFormFactory($file);
650
        $form = $scaffolder->getForm($this, $name, $augmentedContext);
651
652
        // Configure form to respond to validation errors with form schema
653
        // if requested via react.
654
        $form->setValidationResponseCallback(function (ValidationResult $error) use ($form, $id, $name) {
655
            $schemaId = Controller::join_links($this->Link('schema'), $name, $id);
656
            return $this->getSchemaResponse($schemaId, $form, $error);
657
        });
658
659
        return $form;
660
    }
661
662
    /**
663
     * Get form for selecting a file
664
     *
665
     * @return Form
666
     */
667
    public function fileSelectForm()
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->getFileSelectForm($id);
673
    }
674
675
    /**
676
     * Get form for selecting a file
677
     *
678
     * @param int $id ID of the record being selected
679
     * @return Form
680
     */
681
    public function getFileSelectForm($id)
682
    {
683
        return $this->getAbstractFileForm($id, 'fileSelectForm', [ 'Type' => AssetFormFactory::TYPE_SELECT ]);
684
    }
685
686
    /**
687
     * @param array $context
688
     * @return Form
689
     * @throws InvalidArgumentException
690
     */
691
    public function getFileHistoryForm($context)
692
    {
693
        // Check context
694
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
695
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
696
        }
697
        $id = $context['RecordID'];
698
        $versionId = $context['RecordVersion'];
699
        if (!$id || !$versionId) {
700
            return $this->httpError(404);
701
        }
702
703
        /** @var File $file */
704
        $file = Versioned::get_version(File::class, $id, $versionId);
705
        if (!$file) {
706
            return $this->httpError(404);
707
        }
708
709 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...
710
            $this->httpError(403, _t(
711
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
712
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
713
                '',
714
                ['ObjectTitle' => $file->i18n_singular_name()]
715
            ));
716
            return null;
717
        }
718
719
        $effectiveContext = array_merge($context, ['Record' => $file]);
720
        /** @var FormFactory $scaffolder */
721
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
722
        $form = $scaffolder->getForm($this, 'fileHistoryForm', $effectiveContext);
723
724
        // Configure form to respond to validation errors with form schema
725
        // if requested via react.
726 View Code Duplication
        $form->setValidationResponseCallback(function (ValidationResult $errors) use ($form, $id, $versionId) {
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...
727
            $schemaId = Controller::join_links($this->Link('schema/fileHistoryForm'), $id, $versionId);
728
            return $this->getSchemaResponse($schemaId, $form, $errors);
729
        });
730
731
        return $form;
732
    }
733
734
    /**
735
     * Gets a JSON schema representing the current edit form.
736
     *
737
     * WARNING: Experimental API.
738
     *
739
     * @param HTTPRequest $request
740
     * @return HTTPResponse
741
     */
742
    public function schema($request)
743
    {
744
        $formName = $request->param('FormName');
745
        if ($formName !== 'fileHistoryForm') {
746
            return parent::schema($request);
747
        }
748
749
        // Get schema for history form
750
        // @todo Eventually all form scaffolding will be based on context rather than record ID
751
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
752
        $itemID = $request->param('ItemID');
753
        $version = $request->param('OtherItemID');
754
        $form = $this->getFileHistoryForm([
755
            'RecordID' => $itemID,
756
            'RecordVersion' => $version,
757
        ]);
758
759
        // Respond with this schema
760
        $response = $this->getResponse();
761
        $response->addHeader('Content-Type', 'application/json');
762
        $schemaID = $this->getRequest()->getURL();
763
        return $this->getSchemaResponse($schemaID, $form);
764
    }
765
766
    /**
767
     * Get file history form
768
     *
769
     * @return Form
770
     */
771
    public function fileHistoryForm()
772
    {
773
        $request = $this->getRequest();
774
        $id = $request->param('ID') ?: $request->postVar('ID');
775
        $version = $request->param('OtherID') ?: $request->postVar('Version');
776
        $form = $this->getFileHistoryForm([
777
            'RecordID' => $id,
778
            'RecordVersion' => $version,
779
        ]);
780
        return $form;
781
    }
782
783
    /**
784
     * @param array $data
785
     * @param Form $form
786
     * @return HTTPResponse
787
     */
788
    public function save($data, $form)
789
    {
790
        return $this->saveOrPublish($data, $form, false);
791
    }
792
793
    /**
794
     * @param array $data
795
     * @param Form $form
796
     * @return HTTPResponse
797
     */
798
    public function publish($data, $form)
799
    {
800
        return $this->saveOrPublish($data, $form, true);
801
    }
802
803
    /**
804
     * Update thisrecord
805
     *
806
     * @param array $data
807
     * @param Form $form
808
     * @param bool $doPublish
809
     * @return HTTPResponse
810
     */
811
    protected function saveOrPublish($data, $form, $doPublish = false)
812
    {
813 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...
814
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
815
                ->addHeader('Content-Type', 'application/json');
816
        }
817
818
        $id = (int) $data['ID'];
819
        /** @var File $record */
820
        $record = DataObject::get_by_id(File::class, $id);
821
822
        if (!$record) {
823
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
824
                ->addHeader('Content-Type', 'application/json');
825
        }
826
827
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
828
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
829
                ->addHeader('Content-Type', 'application/json');
830
        }
831
832
        // check File extension
833
        if (!empty($data['FileFilename'])) {
834
            $extension = File::get_file_extension($data['FileFilename']);
835
            $newClass = File::get_class_for_file_extension($extension);
836
837
            // if the class has changed, cast it to the proper class
838
            if ($record->getClassName() !== $newClass) {
839
                $record = $record->newClassInstance($newClass);
840
841
                // update the allowed category for the new file extension
842
                $category = File::get_app_category($extension);
843
                $record->File->setAllowedCategories($category);
844
            }
845
        }
846
847
        $form->saveInto($record);
848
        $record->write();
849
850
        // Publish this record and owned objects
851
        if ($doPublish) {
852
            $record->publishRecursive();
853
        }
854
855
        // Note: Force return of schema / state in success result
856
        return $this->getRecordUpdatedResponse($record, $form);
0 ignored issues
show
Compatibility introduced by
$record of type object<SilverStripe\ORM\DataObject> is not a sub-type of object<SilverStripe\Assets\File>. It seems like you assume a child class of the class SilverStripe\ORM\DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
857
    }
858
859
    public function unpublish($data, $form)
860
    {
861 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...
862
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
863
                ->addHeader('Content-Type', 'application/json');
864
        }
865
866
        $id = (int) $data['ID'];
867
        /** @var File $record */
868
        $record = DataObject::get_by_id(File::class, $id);
869
870
        if (!$record) {
871
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
872
                ->addHeader('Content-Type', 'application/json');
873
        }
874
875
        if (!$record->canUnpublish()) {
876
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
877
                ->addHeader('Content-Type', 'application/json');
878
        }
879
880
        $record->doUnpublish();
881
        return $this->getRecordUpdatedResponse($record, $form);
882
    }
883
884
    /**
885
     * @param File $file
886
     *
887
     * @return array
888
     */
889
    public function getObjectFromData(File $file)
890
    {
891
        $object = array(
892
            'id' => $file->ID,
893
            'created' => $file->Created,
894
            'lastUpdated' => $file->LastEdited,
895
            'owner' => null,
896
            'parent' => null,
897
            'title' => $file->Title,
898
            'exists' => $file->exists(), // Broken file check
899
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
900
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
901
            'name' => $file->Name,
902
            'filename' => $file->Filename,
903
            '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...
904
            'size' => $file->AbsoluteSize,
905
            'url' => $file->AbsoluteURL,
906
            'published' => $file->isPublished(),
907
            'modified' => $file->isModifiedOnDraft(),
908
            'draft' => $file->isOnDraftOnly(),
909
            'canEdit' => $file->canEdit(),
910
            'canDelete' => $file->canArchive(),
911
        );
912
913
        /** @var Member $owner */
914
        $owner = $file->Owner();
915
916
        if ($owner) {
917
            $object['owner'] = array(
918
                'id' => $owner->ID,
919
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
920
            );
921
        }
922
923
        /** @var Folder $parent */
924
        $parent = $file->Parent();
925
926
        if ($parent) {
927
            $object['parent'] = array(
928
                'id' => $parent->ID,
929
                'title' => $parent->Title,
930
                'filename' => $parent->Filename,
931
            );
932
        }
933
934
        /** @var File $file */
935
        if ($file->getIsImage()) {
936
            // Small thumbnail
937
            $smallWidth = UploadField::config()->get('thumbnail_width');
938
            $smallHeight = UploadField::config()->get('thumbnail_height');
939
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
940
            if ($smallThumbnail && $smallThumbnail->exists()) {
941
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
942
            }
943
944
            // Large thumbnail
945
            $width = $this->config()->get('thumbnail_width');
946
            $height = $this->config()->get('thumbnail_height');
947
            $thumbnail = $file->FitMax($width, $height);
948
            if ($thumbnail && $thumbnail->exists()) {
949
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
950
            }
951
            $object['width'] = $file->Width;
952
            $object['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...
953
        } else {
954
            $object['thumbnail'] = $file->PreviewLink();
955
        }
956
957
        return $object;
958
    }
959
960
    /**
961
     * Returns the files and subfolders contained in the currently selected folder,
962
     * defaulting to the root node. Doubles as search results, if any search parameters
963
     * are set through {@link SearchForm()}.
964
     *
965
     * @param array $params Unsanitised request parameters
966
     * @return DataList
967
     */
968
    protected function getList($params = array())
969
    {
970
        $context = $this->getSearchContext();
971
972
        // Overwrite name filter to search both Name and Title attributes
973
        $context->removeFilterByName('Name');
974
975
        // Lazy loaded list. Allows adding new filters through SearchContext.
976
        /** @var DataList $list */
977
        $list = $context->getResults($params);
978
979
        // Re-add previously removed "Name" filter as combined filter
980
        // TODO Replace with composite SearchFilter once that API exists
981
        if (!empty($params['Name'])) {
982
            $list = $list->filterAny(array(
983
                'Name:PartialMatch' => $params['Name'],
984
                'Title:PartialMatch' => $params['Name']
985
            ));
986
        }
987
988
        // Optionally limit search to a folder (non-recursive)
989
        if (!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
990
            $list = $list->filter('ParentID', $params['ParentID']);
991
        }
992
993
        // Date filtering
994 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...
995
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
996
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
997
        }
998 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...
999
            $toDate = new DateField(null, null, $params['CreatedTo']);
1000
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1001
        }
1002
1003
        // Categories
1004
        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...
1005
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1006
            $list = $list->filter('Name:PartialMatch', $extensions);
1007
        }
1008
1009
        // Sort folders first
1010
        $list = $list->sort(
1011
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1012
        );
1013
1014
        // Pagination
1015
        if (isset($filters['page']) && isset($filters['limit'])) {
1016
            $page = $filters['page'];
1017
            $limit = $filters['limit'];
1018
            $offset = ($page - 1) * $limit;
1019
            $list = $list->limit($limit, $offset);
1020
        }
1021
1022
        // Access checks
1023
        $list = $list->filterByCallback(function (File $file) {
1024
            return $file->canView();
1025
        });
1026
1027
        return $list;
1028
    }
1029
1030
    /**
1031
     * Action handler for adding pages to a campaign
1032
     *
1033
     * @param array $data
1034
     * @param Form $form
1035
     * @return DBHTMLText|HTTPResponse
1036
     */
1037
    public function addtocampaign($data, $form)
1038
    {
1039
        $id = $data['ID'];
1040
        $record = $this->getList()->byID($id);
1041
1042
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1043
        $results = $handler->addToCampaign($record, $data['Campaign']);
1044
        if (!isset($results)) {
1045
            return null;
1046
        }
1047
1048
        // Send extra "message" data with schema response
1049
        $extraData = ['message' => $results];
1050
        $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1051
        return $this->getSchemaResponse($schemaId, $form, null, $extraData);
1052
    }
1053
1054
    /**
1055
     * Url handler for add to campaign form
1056
     *
1057
     * @param HTTPRequest $request
1058
     * @return Form
1059
     */
1060
    public function addToCampaignForm($request)
1061
    {
1062
        // Get ID either from posted back value, or url parameter
1063
        $id = $request->param('ID') ?: $request->postVar('ID');
1064
        return $this->getAddToCampaignForm($id);
1065
    }
1066
1067
    /**
1068
     * @param int $id
1069
     * @return Form
1070
     */
1071
    public function getAddToCampaignForm($id)
1072
    {
1073
        // Get record-specific fields
1074
        $record = $this->getList()->byID($id);
1075
1076
        if (!$record) {
1077
            $this->httpError(404, _t(
1078
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1079
                'That {Type} couldn\'t be found',
1080
                '',
1081
                ['Type' => File::singleton()->i18n_singular_name()]
1082
            ));
1083
            return null;
1084
        }
1085 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...
1086
            $this->httpError(403, _t(
1087
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1088
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1089
                '',
1090
                ['ObjectTitle' => $record->i18n_singular_name()]
1091
            ));
1092
            return null;
1093
        }
1094
1095
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1096
        $form = $handler->Form($record);
1097
1098 View Code Duplication
        $form->setValidationResponseCallback(function (ValidationResult $errors) 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...
1099
            $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1100
            return $this->getSchemaResponse($schemaId, $form, $errors);
1101
        });
1102
1103
        return $form;
1104
    }
1105
1106
    /**
1107
     * @return Upload
1108
     */
1109
    protected function getUpload()
1110
    {
1111
        $upload = Upload::create();
1112
        $upload->getValidator()->setAllowedExtensions(
1113
            // filter out '' since this would be a regex problem on JS end
1114
            array_filter(File::config()->get('allowed_extensions'))
1115
        );
1116
1117
        return $upload;
1118
    }
1119
1120
    /**
1121
     * Get response for successfully updated record
1122
     *
1123
     * @param File $record
1124
     * @param Form $form
1125
     * @return HTTPResponse
1126
     */
1127
    protected function getRecordUpdatedResponse($record, $form)
1128
    {
1129
        // Return the record data in the same response as the schema to save a postback
1130
        $schemaData = ['record' => $this->getObjectFromData($record)];
1131
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->ID);
1132
        return $this->getSchemaResponse($schemaId, $form, null, $schemaData);
1133
    }
1134
}
1135