Completed
Pull Request — master (#322)
by Damian
01:59
created

AssetAdmin   F

Complexity

Total Complexity 113

Size/Duplication

Total Lines 1020
Duplicated Lines 10.59 %

Coupling/Cohesion

Components 1
Dependencies 28

Importance

Changes 25
Bugs 1 Features 2
Metric Value
wmc 113
c 25
b 1
f 2
lcom 1
cbo 28
dl 108
loc 1020
rs 0.9391

30 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 14 1
A getClientConfig() 0 49 1
C apiReadFolder() 0 66 13
A apiSearch() 0 17 1
C apiDelete() 11 38 8
C apiCreateFile() 25 72 11
C apiCreateFolder() 3 58 12
A legacyRedirectForEditView() 0 8 2
A getFileEditLink() 0 13 3
A getSearchContext() 0 53 1
A getNameGenerator() 0 5 1
A breadcrumbs() 0 4 1
A baseCSSClasses() 0 4 1
A providePermissions() 0 11 1
A getFormFactory() 0 13 3
B getFileEditForm() 13 29 2
A FileEditForm() 7 7 2
A getFileInsertForm() 9 23 2
A FileInsertForm() 7 7 2
A save() 0 4 1
A publish() 0 4 1
C saveOrPublish() 8 32 8
B unpublish() 4 24 5
C getObjectFromData() 0 68 10
C getList() 8 61 10
A addtocampaign() 0 23 3
A AddToCampaignForm() 0 6 2
B getAddToCampaignForm() 13 34 3
A getUpload() 0 10 1
A getRecordUpdatedResponse() 0 14 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AssetAdmin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AssetAdmin, and based on these observations, apply Extract Interface, too.

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\UploadField;
9
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
10
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
11
use SilverStripe\AssetAdmin\Forms\ImageFormFactory;
12
use SilverStripe\Assets\File;
13
use SilverStripe\Assets\Folder;
14
use SilverStripe\Assets\Image;
15
use SilverStripe\Assets\Storage\AssetNameGenerator;
16
use SilverStripe\Assets\Upload;
17
use SilverStripe\Control\Controller;
18
use SilverStripe\Control\HTTPRequest;
19
use SilverStripe\Control\HTTPResponse;
20
use SilverStripe\Core\Convert;
21
use SilverStripe\Core\Injector\Injector;
22
use SilverStripe\Forms\CheckboxField;
23
use SilverStripe\Forms\DateField;
24
use SilverStripe\Forms\DropdownField;
25
use SilverStripe\Forms\FieldGroup;
26
use SilverStripe\Forms\Form;
27
use SilverStripe\Forms\FormFactory;
28
use SilverStripe\Forms\HeaderField;
29
use SilverStripe\ORM\ArrayList;
30
use SilverStripe\ORM\DataList;
31
use SilverStripe\ORM\DataObject;
32
use SilverStripe\ORM\FieldType\DBHTMLText;
33
use SilverStripe\ORM\Search\SearchContext;
34
use SilverStripe\ORM\ValidationResult;
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
    );
98
99
    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...
100
101
    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...
102
103
    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...
104
105
    /**
106
     * Set up the controller
107
     */
108
    public function init()
109
    {
110
        parent::init();
111
112
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
113
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
114
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
115
116
        CMSBatchActionHandler::register(
117
            'delete',
118
            'SilverStripe\AssetAdmin\BatchAction\DeleteAssets',
119
            'SilverStripe\\Assets\\Folder'
120
        );
121
    }
122
123
    public function getClientConfig()
124
    {
125
        $baseLink = $this->Link();
126
        return array_merge(parent::getClientConfig(), [
127
            'reactRouter' => true,
128
            'createFileEndpoint' => [
129
                'url' => Controller::join_links($baseLink, 'api/createFile'),
130
                'method' => 'post',
131
                'payloadFormat' => 'urlencoded',
132
            ],
133
            'createFolderEndpoint' => [
134
                'url' => Controller::join_links($baseLink, 'api/createFolder'),
135
                'method' => 'post',
136
                'payloadFormat' => 'urlencoded',
137
            ],
138
            'readFolderEndpoint' => [
139
                'url' => Controller::join_links($baseLink, 'api/readFolder'),
140
                'method' => 'get',
141
                'responseFormat' => 'json',
142
            ],
143
            'searchEndpoint' => [
144
                'url' => Controller::join_links($baseLink, 'api/search'),
145
                'method' => 'get',
146
                'responseFormat' => 'json',
147
            ],
148
            'updateFolderEndpoint' => [
149
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
150
                'method' => 'put',
151
                'payloadFormat' => 'urlencoded',
152
            ],
153
            'deleteEndpoint' => [
154
                'url' => Controller::join_links($baseLink, 'api/delete'),
155
                'method' => 'delete',
156
                'payloadFormat' => 'urlencoded',
157
            ],
158
            'limit' => $this->config()->page_length,
159
            'form' => [
160
                'FileEditForm' => [
161
                    'schemaUrl' => $this->Link('schema/FileEditForm')
162
                ],
163
                'FileInsertForm' => [
164
                    'schemaUrl' => $this->Link('schema/FileInsertForm')
165
                ],
166
                'AddToCampaignForm' => [
167
                    'schemaUrl' => $this->Link('schema/AddToCampaignForm')
168
                ],
169
            ],
170
        ]);
171
    }
172
173
    /**
174
     * Fetches a collection of files by ParentID.
175
     *
176
     * @param HTTPRequest $request
177
     * @return HTTPResponse
178
     */
179
    public function apiReadFolder(HTTPRequest $request)
180
    {
181
        $params = $request->requestVars();
182
        $items = array();
183
        $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...
184
        $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...
185
186
        if (!isset($params['id']) && !strlen($params['id'])) {
187
            $this->httpError(400);
188
        }
189
190
        $folderID = (int)$params['id'];
191
        /** @var Folder $folder */
192
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
193
194
        if (!$folder) {
195
            $this->httpError(400);
196
        }
197
198
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
199
        $files = $this->getList()->filter('ParentID', $folderID);
200
201
        if ($files) {
202
            /** @var File $file */
203
            foreach ($files as $file) {
204
                if (!$file->canView()) {
205
                    continue;
206
                }
207
208
                $items[] = $this->getObjectFromData($file);
209
            }
210
        }
211
212
        // Build parents (for breadcrumbs)
213
        $parents = [];
214
        $next = $folder->Parent();
215
        while ($next && $next->exists()) {
216
            array_unshift($parents, [
217
                'id' => $next->ID,
218
                'title' => $next->getTitle(),
219
                'filename' => $next->getFilename(),
220
            ]);
221
            if ($next->ParentID) {
222
                $next = $next->Parent();
223
            } else {
224
                break;
225
            }
226
        }
227
228
        // Build response
229
        $response = new HTTPResponse();
230
        $response->addHeader('Content-Type', 'application/json');
231
        $response->setBody(json_encode([
232
            'files' => $items,
233
            'title' => $folder->getTitle(),
234
            'count' => count($items),
235
            'parents' => $parents,
236
            'parent' => $parents ? $parents[count($parents) - 1] : null,
237
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
238
            'folderID' => $folderID,
239
            'canEdit' => $folder->canEdit(),
240
            'canDelete' => $folder->canArchive(),
241
        ]));
242
243
        return $response;
244
    }
245
246
    /**
247
     * @param HTTPRequest $request
248
     *
249
     * @return HTTPResponse
250
     */
251
    public function apiSearch(HTTPRequest $request)
252
    {
253
        $params = $request->getVars();
254
        $list = $this->getList($params);
255
256
        $response = new HTTPResponse();
257
        $response->addHeader('Content-Type', 'application/json');
258
        $response->setBody(json_encode([
259
            // Serialisation
260
            "files" => array_map(function ($file) {
261
                return $this->getObjectFromData($file);
262
            }, $list->toArray()),
263
            "count" => $list->count(),
264
        ]));
265
266
        return $response;
267
    }
268
269
    /**
270
     * @param HTTPRequest $request
271
     *
272
     * @return HTTPResponse
273
     */
274
    public function apiDelete(HTTPRequest $request)
275
    {
276
        parse_str($request->getBody(), $vars);
277
278
        // CSRF check
279
        $token = SecurityToken::inst();
280 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...
281
            return new HTTPResponse(null, 400);
282
        }
283
284 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...
285
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
286
                ->addHeader('Content-Type', 'application/json');
287
        }
288
289
        $fileIds = $vars['ids'];
290
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
291
292 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...
293
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
294
                ->addHeader('Content-Type', 'application/json');
295
        }
296
297
        if (!min(array_map(function (File $file) {
298
            return $file->canArchive();
299
        }, $files))) {
300
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
301
                ->addHeader('Content-Type', 'application/json');
302
        }
303
304
        /** @var File $file */
305
        foreach ($files as $file) {
306
            $file->doArchive();
307
        }
308
309
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
310
            ->addHeader('Content-Type', 'application/json');
311
    }
312
313
    /**
314
     * Creates a single file based on a form-urlencoded upload.
315
     *
316
     * @param HTTPRequest $request
317
     * @return HTTPRequest|HTTPResponse
318
     */
319
    public function apiCreateFile(HTTPRequest $request)
320
    {
321
        $data = $request->postVars();
322
        $upload = $this->getUpload();
323
324
        // CSRF check
325
        $token = SecurityToken::inst();
326 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...
327
            return new HTTPResponse(null, 400);
328
        }
329
330
        // Check parent record
331
        /** @var Folder $parentRecord */
332
        $parentRecord = null;
333
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
334
            $parentRecord = Folder::get()->byID($data['ParentID']);
335
        }
336
        $data['Parent'] = $parentRecord;
337
338
        $tmpFile = $request->postVar('Upload');
339
        if (!$upload->validate($tmpFile)) {
340
            $result = ['message' => null];
341
            $errors = $upload->getErrors();
342
            if ($message = array_shift($errors)) {
343
                $result['message'] = [
344
                    'type' => 'error',
345
                    'value' => $message,
346
                ];
347
            }
348
            return (new HTTPResponse(json_encode($result), 400))
349
                ->addHeader('Content-Type', 'application/json');
350
        }
351
352
        // TODO Allow batch uploads
353
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
354
        /** @var File $file */
355
        $file = Injector::inst()->create($fileClass);
356
357
        // check canCreate permissions
358 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...
359
            $result = ['message' => [
360
                'type' => 'error',
361
                'value' => _t(
362
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
363
                    'You do not have permission to add files'
364
                )
365
            ]];
366
            return (new HTTPResponse(json_encode($result), 403))
367
                ->addHeader('Content-Type', 'application/json');
368
        }
369
370
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
371 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...
372
            $result = ['message' => [
373
                'type' => 'error',
374
                'value' => _t(
375
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
376
                    'Failed to load file'
377
                )
378
            ]];
379
            return (new HTTPResponse(json_encode($result), 400))
380
                ->addHeader('Content-Type', 'application/json');
381
        }
382
383
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
384
        $file->write();
385
386
        $result = [$this->getObjectFromData($file)];
387
388
        return (new HTTPResponse(json_encode($result)))
389
            ->addHeader('Content-Type', 'application/json');
390
    }
391
392
    /**
393
     * Creates a single folder, within an optional parent folder.
394
     *
395
     * @param HTTPRequest $request
396
     * @return HTTPRequest|HTTPResponse
397
     */
398
    public function apiCreateFolder(HTTPRequest $request)
399
    {
400
        $data = $request->postVars();
401
402
        $class = 'SilverStripe\\Assets\\Folder';
403
404
        // CSRF check
405
        $token = SecurityToken::inst();
406 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...
407
            return new HTTPResponse(null, 400);
408
        }
409
410
        // check addchildren permissions
411
        /** @var Folder $parentRecord */
412
        $parentRecord = null;
413
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
414
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
415
        }
416
        $data['Parent'] = $parentRecord;
417
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
418
419
        // Build filename
420
        $baseFilename = isset($data['Name'])
421
            ? basename($data['Name'])
422
            : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NEWFOLDER', "NewFolder");
423
424
        if ($parentRecord && $parentRecord->ID) {
425
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
426
        }
427
428
        // Ensure name is unique
429
        $nameGenerator = $this->getNameGenerator($baseFilename);
430
        $filename = null;
431
        foreach ($nameGenerator as $filename) {
432
            if (! File::find($filename)) {
433
                break;
434
            }
435
        }
436
        $data['Name'] = basename($filename);
437
438
        // Create record
439
        /** @var Folder $record */
440
        $record = Injector::inst()->create($class);
441
442
        // check create permissions
443
        if (!$record->canCreate(null, $data)) {
444
            return (new HTTPResponse(null, 403))
445
                ->addHeader('Content-Type', 'application/json');
446
        }
447
448
        $record->ParentID = $data['ParentID'];
449
        $record->Name = $record->Title = basename($data['Name']);
450
        $record->write();
451
452
        $result = $this->getObjectFromData($record);
453
454
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
455
    }
456
457
    /**
458
     * Redirects 3.x style detail links to new 4.x style routing.
459
     *
460
     * @param HTTPRequest $request
461
     */
462
    public function legacyRedirectForEditView($request)
463
    {
464
        $fileID = $request->param('FileID');
465
        /** @var File $file */
466
        $file = File::get()->byID($fileID);
467
        $link = $this->getFileEditLink($file) ?: $this->Link();
468
        $this->redirect($link);
469
    }
470
471
    /**
472
     * Given a file return the CMS link to edit it
473
     *
474
     * @param File $file
475
     * @return string
476
     */
477
    public function getFileEditLink($file)
478
    {
479
        if (!$file || !$file->isInDB()) {
480
            return null;
481
        }
482
483
        return Controller::join_links(
484
            $this->Link('show'),
485
            $file->ParentID,
486
            'edit',
487
            $file->ID
488
        );
489
    }
490
491
    /**
492
     * Get the search context from {@link File}, used to create the search form
493
     * as well as power the /search API endpoint.
494
     *
495
     * @return SearchContext
496
     */
497
    public function getSearchContext()
498
    {
499
        $context = File::singleton()->getDefaultSearchContext();
500
501
        // Customize fields
502
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
503
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
504
        ->setConfig('showcalendar', true);
505
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
506
        ->setConfig('showcalendar', true);
507
        $dateGroup = FieldGroup::create(
508
            $dateHeader,
509
            $dateFrom,
510
            $dateTo
511
        );
512
        $context->addField($dateGroup);
513
        /** @skipUpgrade */
514
        $appCategories = array(
515
            'archive' => _t(
516
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryArchive',
517
                'Archive'
518
            ),
519
            'audio' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryAudio', 'Audio'),
520
            'document' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryDocument', 'Document'),
521
            'flash' => _t(
522
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryFlash',
523
                'Flash',
524
                'The fileformat'
525
            ),
526
            'image' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryImage', 'Image'),
527
            'video' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryVideo', 'Video'),
528
        );
529
        $context->addField(
530
            $typeDropdown = new DropdownField(
531
                'AppCategory',
532
                _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.Filetype', 'File type'),
533
                $appCategories
534
            )
535
        );
536
537
        $typeDropdown->setEmptyString(' ');
538
539
        $currentfolderLabel = _t(
540
            'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CurrentFolderOnly',
541
            'Limit to current folder?'
542
        );
543
        $context->addField(
544
            new CheckboxField('CurrentFolderOnly', $currentfolderLabel)
545
        );
546
        $context->getFields()->removeByName('Title');
547
548
        return $context;
549
    }
550
551
    /**
552
     * Get an asset renamer for the given filename.
553
     *
554
     * @param  string            $filename Path name
555
     * @return AssetNameGenerator
556
     */
557
    protected function getNameGenerator($filename)
558
    {
559
        return Injector::inst()
560
            ->createWithArgs('AssetNameGenerator', array($filename));
561
    }
562
563
    /**
564
     * @todo Implement on client
565
     *
566
     * @param bool $unlinked
567
     * @return ArrayList
568
     */
569
    public function breadcrumbs($unlinked = false)
570
    {
571
        return null;
572
    }
573
574
575
    /**
576
     * Don't include class namespace in auto-generated CSS class
577
     */
578
    public function baseCSSClasses()
579
    {
580
        return 'AssetAdmin LeftAndMain';
581
    }
582
583
    public function providePermissions()
584
    {
585
        return array(
586
            "CMS_ACCESS_AssetAdmin" => array(
587
                '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...
588
                    'title' => static::menu_title()
589
                )),
590
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
591
            )
592
        );
593
    }
594
595
    /**
596
     * Build a form scaffolder for this model
597
     *
598
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
599
     *
600
     * @param File $file
601
     * @return FormFactory
602
     */
603
    public function getFormFactory(File $file)
604
    {
605
        // Get service name based on file class
606
        $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...
607
        if ($file instanceof Folder) {
608
            $name = FolderFormFactory::class;
609
        } elseif ($file instanceof Image) {
610
            $name = ImageFormFactory::class;
611
        } else {
612
            $name = FileFormFactory::class;
613
        }
614
        return Injector::inst()->get($name);
615
    }
616
617
    /**
618
     * The form is used to generate a form schema,
619
     * as well as an intermediary object to process data through API endpoints.
620
     * Since it's used directly on API endpoints, it does not have any form actions.
621
     * It handles both {@link File} and {@link Folder} records.
622
     *
623
     * @param int $id
624
     * @return Form
625
     */
626
    public function getFileEditForm($id)
627
    {
628
        /** @var File $file */
629
        $file = $this->getList()->byID($id);
630
631 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...
632
            $this->httpError(403, _t(
633
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
634
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
635
                '',
636
                ['ObjectTitle' => $file->i18n_singular_name()]
637
            ));
638
            return null;
639
        }
640
641
        $scaffolder = $this->getFormFactory($file);
642
        $form = $scaffolder->getForm($this, 'FileEditForm', [
643
            'Record' => $file
644
        ]);
645
646
        // Configure form to respond to validation errors with form schema
647
        // if requested via react.
648 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...
649
            $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $id);
650
            return $this->getSchemaResponse($schemaId, $form, $errors);
0 ignored issues
show
Documentation introduced by
$schemaId is of type string, but the function expects a object<SilverStripe\Forms\Form>.

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...
Unused Code introduced by
The call to AssetAdmin::getSchemaResponse() has too many arguments starting with $errors.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
651
        });
652
653
        return $form;
654
    }
655
656
    /**
657
     * Get file edit form
658
     *
659
     * @return Form
660
     */
661 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...
662
    {
663
        // Get ID either from posted back value, or url parameter
664
        $request = $this->getRequest();
665
        $id = $request->param('ID') ?: $request->postVar('ID');
666
        return $this->getFileEditForm($id);
667
    }
668
669
    /**
670
     * The form is used to generate a form schema,
671
     * as well as an intermediary object to process data through API endpoints.
672
     * Since it's used directly on API endpoints, it does not have any form actions.
673
     * It handles both {@link File} and {@link Folder} records.
674
     *
675
     * @param int $id
676
     * @return Form
677
     */
678
    public function getFileInsertForm($id)
679
    {
680
        /** @var File $file */
681
        $file = $this->getList()->byID($id);
682
683 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...
684
            $this->httpError(403, _t(
685
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
686
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
687
                '',
688
                ['ObjectTitle' => $file->i18n_singular_name()]
689
            ));
690
            return null;
691
        }
692
693
        $scaffolder = $this->getFormFactory($file);
694
        $form = $scaffolder->getForm($this, 'FileInsertForm', [
695
            'Record' => $file,
696
            'Type' => 'insert',
697
        ]);
698
699
        return $form;
700
    }
701
702
    /**
703
     * Get file insert form
704
     *
705
     * @return Form
706
     */
707 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...
708
    {
709
        // Get ID either from posted back value, or url parameter
710
        $request = $this->getRequest();
711
        $id = $request->param('ID') ?: $request->postVar('ID');
712
        return $this->getFileInsertForm($id);
713
    }
714
715
    /**
716
     * @param array $data
717
     * @param Form $form
718
     * @return HTTPResponse
719
     */
720
    public function save($data, $form)
721
    {
722
        return $this->saveOrPublish($data, $form, false);
723
    }
724
725
    /**
726
     * @param array $data
727
     * @param Form $form
728
     * @return HTTPResponse
729
     */
730
    public function publish($data, $form)
731
    {
732
        return $this->saveOrPublish($data, $form, true);
733
    }
734
735
    /**
736
     * Update thisrecord
737
     *
738
     * @param array $data
739
     * @param Form $form
740
     * @param bool $doPublish
741
     * @return HTTPResponse
742
     */
743
    protected function saveOrPublish($data, $form, $doPublish = false)
744
    {
745 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...
746
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
747
                ->addHeader('Content-Type', 'application/json');
748
        }
749
750
        $id = (int) $data['ID'];
751
        /** @var File $record */
752
        $record = $this->getList()->filter('ID', $id)->first();
753
754 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...
755
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
756
                ->addHeader('Content-Type', 'application/json');
757
        }
758
759
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
760
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
761
                ->addHeader('Content-Type', 'application/json');
762
        }
763
764
        $form->saveInto($record);
765
        $record->write();
766
767
        // Publish this record and owned objects
768
        if ($doPublish) {
769
            $record->publishRecursive();
770
        }
771
772
        // Note: Force return of schema / state in success result
773
        return $this->getRecordUpdatedResponse($record, $form);
774
    }
775
776
    public function unpublish($data, $form)
777
    {
778 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...
779
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
780
                ->addHeader('Content-Type', 'application/json');
781
        }
782
783
        $id = (int) $data['ID'];
784
        /** @var File $record */
785
        $record = $this->getList()->filter('ID', $id)->first();
786
787
        if (!$record) {
788
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
789
                ->addHeader('Content-Type', 'application/json');
790
        }
791
792
        if (!$record->canUnpublish()) {
793
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
794
                ->addHeader('Content-Type', 'application/json');
795
        }
796
797
        $record->doUnpublish();
798
        return $this->getRecordUpdatedResponse($record, $form);
799
    }
800
801
    /**
802
     * @param File $file
803
     *
804
     * @return array
805
     */
806
    public function getObjectFromData(File $file)
807
    {
808
        $object = array(
809
            'id' => $file->ID,
810
            'created' => $file->Created,
811
            'lastUpdated' => $file->LastEdited,
812
            'owner' => null,
813
            'parent' => null,
814
            'title' => $file->Title,
815
            'exists' => $file->exists(), // Broken file check
816
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
817
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
818
            'name' => $file->Name,
819
            'filename' => $file->Filename,
820
            '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...
821
            'size' => $file->Size,
822
            'url' => $file->AbsoluteURL,
823
            'published' => $file->isPublished(),
824
            'modified' => $file->isModifiedOnDraft(),
825
            'draft' => $file->isOnDraftOnly(),
826
            'canEdit' => $file->canEdit(),
827
            'canDelete' => $file->canArchive(),
828
        );
829
830
        /** @var Member $owner */
831
        $owner = $file->Owner();
832
833
        if ($owner) {
834
            $object['owner'] = array(
835
                'id' => $owner->ID,
836
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
837
            );
838
        }
839
840
        /** @var Folder $parent */
841
        $parent = $file->Parent();
842
843
        if ($parent) {
844
            $object['parent'] = array(
845
                'id' => $parent->ID,
846
                'title' => $parent->Title,
847
                'filename' => $parent->Filename,
848
            );
849
        }
850
851
        /** @var File $file */
852
        if ($file->getIsImage()) {
853
            // Small thumbnail
854
            $smallWidth = UploadField::config()->get('thumbnail_width');
855
            $smallHeight = UploadField::config()->get('thumbnail_height');
856
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
857
            if ($smallThumbnail && $smallThumbnail->exists()) {
858
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
859
            }
860
861
            // Large thumbnail
862
            $width = $this->config()->get('thumbnail_width');
863
            $height = $this->config()->get('thumbnail_height');
864
            $thumbnail = $file->FitMax($width, $height);
865
            if ($thumbnail && $thumbnail->exists()) {
866
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
867
            }
868
            $object['dimensions']['width'] = $file->Width;
869
            $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...
870
        }
871
872
        return $object;
873
    }
874
875
    /**
876
     * Returns the files and subfolders contained in the currently selected folder,
877
     * defaulting to the root node. Doubles as search results, if any search parameters
878
     * are set through {@link SearchForm()}.
879
     *
880
     * @param array $params Unsanitised request parameters
881
     * @return DataList
882
     */
883
    protected function getList($params = array())
884
    {
885
        $context = $this->getSearchContext();
886
887
        // Overwrite name filter to search both Name and Title attributes
888
        $context->removeFilterByName('Name');
889
890
        // Lazy loaded list. Allows adding new filters through SearchContext.
891
        /** @var DataList $list */
892
        $list = $context->getResults($params);
893
894
        // Re-add previously removed "Name" filter as combined filter
895
        // TODO Replace with composite SearchFilter once that API exists
896
        if (!empty($params['Name'])) {
897
            $list = $list->filterAny(array(
898
                'Name:PartialMatch' => $params['Name'],
899
                'Title:PartialMatch' => $params['Name']
900
            ));
901
        }
902
903
        // Optionally limit search to a folder (non-recursive)
904
        if (!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
905
            $list = $list->filter('ParentID', $params['ParentID']);
906
        }
907
908
        // Date filtering
909 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...
910
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
911
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
912
        }
913 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...
914
            $toDate = new DateField(null, null, $params['CreatedTo']);
915
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
916
        }
917
918
        // Categories
919
        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...
920
            $extensions = File::config()->app_categories[$filters['AppCategory']];
921
            $list = $list->filter('Name:PartialMatch', $extensions);
922
        }
923
924
        // Sort folders first
925
        $list = $list->sort(
926
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
927
        );
928
929
        // Pagination
930
        if (isset($filters['page']) && isset($filters['limit'])) {
931
            $page = $filters['page'];
932
            $limit = $filters['limit'];
933
            $offset = ($page - 1) * $limit;
934
            $list = $list->limit($limit, $offset);
935
        }
936
937
        // Access checks
938
        $list = $list->filterByCallback(function (File $file) {
939
            return $file->canView();
940
        });
941
942
        return $list;
943
    }
944
945
    /**
946
     * Action handler for adding pages to a campaign
947
     *
948
     * @param array $data
949
     * @param Form $form
950
     * @return DBHTMLText|HTTPResponse
951
     */
952
    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...
953
    {
954
        $id = $data['ID'];
955
        $record = $this->getList()->byID($id);
956
957
        $handler = AddToCampaignHandler::create($this, $record);
958
        $results = $handler->addToCampaign($record, $data['Campaign']);
959
        if (!isset($results)) {
960
            return null;
961
        }
962
963
            $request = $this->getRequest();
964
        if ($request->getHeader('X-Formschema-Request')) {
965
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
966
            $data = $this->getSchemaForForm($schemaId, $handler->Form($record));
0 ignored issues
show
Documentation introduced by
$schemaId is of type string, but the function expects a object<SilverStripe\Forms\Form>.

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...
967
            $data['message'] = $results;
968
969
            $response = new HTTPResponse(Convert::raw2json($data));
970
            $response->addHeader('Content-Type', 'application/json');
971
            return $response;
972
        }
973
            return $results;
974
    }
975
976
    /**
977
     * Url handler for add to campaign form
978
     *
979
     * @param HTTPRequest $request
980
     * @return Form
981
     */
982
    public function AddToCampaignForm($request)
983
    {
984
        // Get ID either from posted back value, or url parameter
985
        $id = $request->param('ID') ?: $request->postVar('ID');
986
        return $this->getAddToCampaignForm($id);
987
    }
988
989
    /**
990
     * @param int $id
991
     * @return Form
992
     */
993
    public function getAddToCampaignForm($id)
994
    {
995
        // Get record-specific fields
996
        $record = $this->getList()->byID($id);
997
998
        if (!$record) {
999
            $this->httpError(404, _t(
1000
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1001
                'That {Type} couldn\'t be found',
1002
                '',
1003
                ['Type' => File::singleton()->i18n_singular_name()]
1004
            ));
1005
            return null;
1006
        }
1007 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...
1008
            $this->httpError(403, _t(
1009
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1010
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1011
                '',
1012
                ['ObjectTitle' => $record->i18n_singular_name()]
1013
            ));
1014
            return null;
1015
        }
1016
1017
        $handler = AddToCampaignHandler::create($this, $record);
1018
        $form = $handler->Form($record);
1019
1020 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...
1021
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1022
            return $this->getSchemaResponse($schemaId, $form, $errors);
0 ignored issues
show
Documentation introduced by
$schemaId is of type string, but the function expects a object<SilverStripe\Forms\Form>.

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...
Unused Code introduced by
The call to AssetAdmin::getSchemaResponse() has too many arguments starting with $errors.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1023
        });
1024
1025
        return $form;
1026
    }
1027
1028
    /**
1029
     * @return Upload
1030
     */
1031
    protected function getUpload()
1032
    {
1033
        $upload = Upload::create();
1034
        $upload->getValidator()->setAllowedExtensions(
1035
            // filter out '' since this would be a regex problem on JS end
1036
            array_filter(File::config()->get('allowed_extensions'))
1037
        );
1038
1039
        return $upload;
1040
    }
1041
1042
    /**
1043
     * Get response for successfully updated record
1044
     *
1045
     * @param File $record
1046
     * @param Form $form
1047
     * @return HTTPResponse
1048
     */
1049
    protected function getRecordUpdatedResponse($record, $form)
1050
    {
1051
        // Note: Force return of schema / state in success result
1052
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->ID);
1053
        $schemaData = $this->getSchemaForForm($schemaId, $form);
0 ignored issues
show
Documentation introduced by
$schemaId is of type string, but the function expects a object<SilverStripe\Forms\Form>.

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...
1054
1055
        // Return the record data in the same response as the schema to save a postback
1056
        $schemaData['record'] = $this->getObjectFromData($record);
1057
1058
        // Serialise
1059
        $response = new HTTPResponse(Convert::raw2json($schemaData));
1060
        $response->addHeader('Content-Type', 'application/json');
1061
        return $response;
1062
    }
1063
}
1064