Completed
Push — master ( 84256f...dfcf1e )
by Will
9s
created

AssetAdmin::getAbstractFileForm()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 16

Duplication

Lines 9
Ratio 31.03 %

Importance

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

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...
823
        });
824
825
        return $form;
826
    }
827
828
    /**
829
     * Get form for selecting a file
830
     *
831
     * @return Form
832
     */
833
    public function fileSelectForm()
834
    {
835
        // Get ID either from posted back value, or url parameter
836
        $request = $this->getRequest();
837
        $id = $request->param('ID') ?: $request->postVar('ID');
838
        return $this->getFileSelectForm($id);
839
    }
840
841
    /**
842
     * Get form for selecting a file
843
     *
844
     * @param int $id ID of the record being selected
845
     * @return Form
846
     */
847
    public function getFileSelectForm($id)
848
    {
849
        return $this->getAbstractFileForm($id, 'fileSelectForm', [ 'Type' => AssetFormFactory::TYPE_SELECT ]);
850
    }
851
852
    /**
853
     * @param array $context
854
     * @return Form
855
     * @throws InvalidArgumentException
856
     */
857
    public function getFileHistoryForm($context)
858
    {
859
        // Check context
860
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
861
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
862
        }
863
        $id = $context['RecordID'];
864
        $versionId = $context['RecordVersion'];
865
        if (!$id || !$versionId) {
866
            return $this->httpError(404);
867
        }
868
869
        /** @var File $file */
870
        $file = Versioned::get_version(File::class, $id, $versionId);
871
        if (!$file) {
872
            return $this->httpError(404);
873
        }
874
875 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...
876
            $this->httpError(403, _t(
877
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
878
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
879
                '',
880
                ['ObjectTitle' => $file->i18n_singular_name()]
881
            ));
882
            return null;
883
        }
884
885
        $effectiveContext = array_merge($context, ['Record' => $file]);
886
        /** @var FormFactory $scaffolder */
887
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
888
        $form = $scaffolder->getForm($this, 'fileHistoryForm', $effectiveContext);
889
890
        // Configure form to respond to validation errors with form schema
891
        // if requested via react.
892 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...
893
            $schemaId = Controller::join_links($this->Link('schema/fileHistoryForm'), $id, $versionId);
894
            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...
895
        });
896
897
        return $form;
898
    }
899
900
    /**
901
     * Gets a JSON schema representing the current edit form.
902
     *
903
     * WARNING: Experimental API.
904
     *
905
     * @param HTTPRequest $request
906
     * @return HTTPResponse
907
     */
908
    public function schema($request)
909
    {
910
        $formName = $request->param('FormName');
911
        if ($formName !== 'fileHistoryForm') {
912
            return parent::schema($request);
913
        }
914
915
        // Get schema for history form
916
        // @todo Eventually all form scaffolding will be based on context rather than record ID
917
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
918
        $itemID = $request->param('ItemID');
919
        $version = $request->param('OtherItemID');
920
        $form = $this->getFileHistoryForm([
921
            'RecordID' => $itemID,
922
            'RecordVersion' => $version,
923
        ]);
924
925
        // Respond with this schema
926
        $response = $this->getResponse();
927
        $response->addHeader('Content-Type', 'application/json');
928
        $schemaID = $this->getRequest()->getURL();
929
        return $this->getSchemaResponse($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...
Bug introduced by
It seems like $form defined by $this->getFileHistoryFor...dVersion' => $version)) on line 920 can also be of type object<SilverStripe\Forms\Form>; however, SilverStripe\Admin\LeftA...in::getSchemaResponse() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
930
    }
931
932
    /**
933
     * Get file history form
934
     *
935
     * @return Form
936
     */
937
    public function fileHistoryForm()
938
    {
939
        $request = $this->getRequest();
940
        $id = $request->param('ID') ?: $request->postVar('ID');
941
        $version = $request->param('OtherID') ?: $request->postVar('Version');
942
        $form = $this->getFileHistoryForm([
943
            'RecordID' => $id,
944
            'RecordVersion' => $version,
945
        ]);
946
        return $form;
947
    }
948
949
    /**
950
     * @param array $data
951
     * @param Form $form
952
     * @return HTTPResponse
953
     */
954
    public function save($data, $form)
955
    {
956
        return $this->saveOrPublish($data, $form, false);
957
    }
958
959
    /**
960
     * @param array $data
961
     * @param Form $form
962
     * @return HTTPResponse
963
     */
964
    public function publish($data, $form)
965
    {
966
        return $this->saveOrPublish($data, $form, true);
967
    }
968
969
    /**
970
     * Update thisrecord
971
     *
972
     * @param array $data
973
     * @param Form $form
974
     * @param bool $doPublish
975
     * @return HTTPResponse
976
     */
977
    protected function saveOrPublish($data, $form, $doPublish = false)
978
    {
979 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...
980
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
981
                ->addHeader('Content-Type', 'application/json');
982
        }
983
984
        $id = (int) $data['ID'];
985
        /** @var File $record */
986
        $record = $this->getList()->filter('ID', $id)->first();
987
988 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...
989
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
990
                ->addHeader('Content-Type', 'application/json');
991
        }
992
993
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
994
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
995
                ->addHeader('Content-Type', 'application/json');
996
        }
997
998
        $form->saveInto($record);
999
        $record->write();
1000
1001
        // Publish this record and owned objects
1002
        if ($doPublish) {
1003
            $record->publishRecursive();
1004
        }
1005
1006
        // Note: Force return of schema / state in success result
1007
        return $this->getRecordUpdatedResponse($record, $form);
1008
    }
1009
1010
    public function unpublish($data, $form)
1011
    {
1012 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...
1013
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
1014
                ->addHeader('Content-Type', 'application/json');
1015
        }
1016
1017
        $id = (int) $data['ID'];
1018
        /** @var File $record */
1019
        $record = $this->getList()->filter('ID', $id)->first();
1020
1021
        if (!$record) {
1022
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
1023
                ->addHeader('Content-Type', 'application/json');
1024
        }
1025
1026
        if (!$record->canUnpublish()) {
1027
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
1028
                ->addHeader('Content-Type', 'application/json');
1029
        }
1030
1031
        $record->doUnpublish();
1032
        return $this->getRecordUpdatedResponse($record, $form);
1033
    }
1034
1035
    /**
1036
     * @param File $file
1037
     *
1038
     * @return array
1039
     */
1040
    public function getObjectFromData(File $file)
1041
    {
1042
        $object = array(
1043
            'id' => $file->ID,
1044
            'created' => $file->Created,
1045
            'lastUpdated' => $file->LastEdited,
1046
            'owner' => null,
1047
            'parent' => null,
1048
            'title' => $file->Title,
1049
            'exists' => $file->exists(), // Broken file check
1050
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
1051
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
1052
            'name' => $file->Name,
1053
            'filename' => $file->Filename,
1054
            '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...
1055
            'size' => $file->Size,
1056
            'url' => $file->AbsoluteURL,
1057
            'published' => $file->isPublished(),
1058
            'modified' => $file->isModifiedOnDraft(),
1059
            'draft' => $file->isOnDraftOnly(),
1060
            'canEdit' => $file->canEdit(),
1061
            'canDelete' => $file->canArchive(),
1062
        );
1063
1064
        /** @var Member $owner */
1065
        $owner = $file->Owner();
1066
1067
        if ($owner) {
1068
            $object['owner'] = array(
1069
                'id' => $owner->ID,
1070
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1071
            );
1072
        }
1073
1074
        /** @var Folder $parent */
1075
        $parent = $file->Parent();
1076
1077
        if ($parent) {
1078
            $object['parent'] = array(
1079
                'id' => $parent->ID,
1080
                'title' => $parent->Title,
1081
                'filename' => $parent->Filename,
1082
            );
1083
        }
1084
1085
        /** @var File $file */
1086
        if ($file->getIsImage()) {
1087
            // Small thumbnail
1088
            $smallWidth = UploadField::config()->get('thumbnail_width');
1089
            $smallHeight = UploadField::config()->get('thumbnail_height');
1090
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1091
            if ($smallThumbnail && $smallThumbnail->exists()) {
1092
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1093
            }
1094
1095
            // Large thumbnail
1096
            $width = $this->config()->get('thumbnail_width');
1097
            $height = $this->config()->get('thumbnail_height');
1098
            $thumbnail = $file->FitMax($width, $height);
1099
            if ($thumbnail && $thumbnail->exists()) {
1100
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1101
            }
1102
            $object['dimensions']['width'] = $file->Width;
1103
            $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...
1104
        }
1105
1106
        return $object;
1107
    }
1108
1109
    /**
1110
     * Returns the files and subfolders contained in the currently selected folder,
1111
     * defaulting to the root node. Doubles as search results, if any search parameters
1112
     * are set through {@link SearchForm()}.
1113
     *
1114
     * @param array $params Unsanitised request parameters
1115
     * @return DataList
1116
     */
1117
    protected function getList($params = array())
1118
    {
1119
        $context = $this->getSearchContext();
1120
1121
        // Overwrite name filter to search both Name and Title attributes
1122
        $context->removeFilterByName('Name');
1123
1124
        // Lazy loaded list. Allows adding new filters through SearchContext.
1125
        /** @var DataList $list */
1126
        $list = $context->getResults($params);
1127
1128
        // Re-add previously removed "Name" filter as combined filter
1129
        // TODO Replace with composite SearchFilter once that API exists
1130
        if (!empty($params['Name'])) {
1131
            $list = $list->filterAny(array(
1132
                'Name:PartialMatch' => $params['Name'],
1133
                'Title:PartialMatch' => $params['Name']
1134
            ));
1135
        }
1136
1137
        // Optionally limit search to a folder (non-recursive)
1138
        if (!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1139
            $list = $list->filter('ParentID', $params['ParentID']);
1140
        }
1141
1142
        // Date filtering
1143 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...
1144
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1145
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1146
        }
1147 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...
1148
            $toDate = new DateField(null, null, $params['CreatedTo']);
1149
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1150
        }
1151
1152
        // Categories
1153
        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...
1154
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1155
            $list = $list->filter('Name:PartialMatch', $extensions);
1156
        }
1157
1158
        // Sort folders first
1159
        $list = $list->sort(
1160
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1161
        );
1162
1163
        // Pagination
1164
        if (isset($filters['page']) && isset($filters['limit'])) {
1165
            $page = $filters['page'];
1166
            $limit = $filters['limit'];
1167
            $offset = ($page - 1) * $limit;
1168
            $list = $list->limit($limit, $offset);
1169
        }
1170
1171
        // Access checks
1172
        $list = $list->filterByCallback(function (File $file) {
1173
            return $file->canView();
1174
        });
1175
1176
        return $list;
1177
    }
1178
1179
    /**
1180
     * Action handler for adding pages to a campaign
1181
     *
1182
     * @param array $data
1183
     * @param Form $form
1184
     * @return DBHTMLText|HTTPResponse
1185
     */
1186
    public function addtocampaign($data, $form)
1187
    {
1188
        $id = $data['ID'];
1189
        $record = $this->getList()->byID($id);
1190
1191
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1192
        $results = $handler->addToCampaign($record, $data['Campaign']);
1193
        if (!isset($results)) {
1194
            return null;
1195
        }
1196
1197
        // Send extra "message" data with schema response
1198
        $extraData = ['message' => $results];
1199
        $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1200
        return $this->getSchemaResponse($schemaId, $form, null, $extraData);
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 null.

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...
1201
    }
1202
1203
    /**
1204
     * Url handler for add to campaign form
1205
     *
1206
     * @param HTTPRequest $request
1207
     * @return Form
1208
     */
1209
    public function addToCampaignForm($request)
1210
    {
1211
        // Get ID either from posted back value, or url parameter
1212
        $id = $request->param('ID') ?: $request->postVar('ID');
1213
        return $this->getAddToCampaignForm($id);
1214
    }
1215
1216
    /**
1217
     * @param int $id
1218
     * @return Form
1219
     */
1220
    public function getAddToCampaignForm($id)
1221
    {
1222
        // Get record-specific fields
1223
        $record = $this->getList()->byID($id);
1224
1225
        if (!$record) {
1226
            $this->httpError(404, _t(
1227
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1228
                'That {Type} couldn\'t be found',
1229
                '',
1230
                ['Type' => File::singleton()->i18n_singular_name()]
1231
            ));
1232
            return null;
1233
        }
1234 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...
1235
            $this->httpError(403, _t(
1236
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1237
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1238
                '',
1239
                ['ObjectTitle' => $record->i18n_singular_name()]
1240
            ));
1241
            return null;
1242
        }
1243
1244
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1245
        $form = $handler->Form($record);
1246
1247 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...
1248
            $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1249
            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...
1250
        });
1251
1252
        return $form;
1253
    }
1254
1255
    /**
1256
     * @return Upload
1257
     */
1258
    protected function getUpload()
1259
    {
1260
        $upload = Upload::create();
1261
        $upload->getValidator()->setAllowedExtensions(
1262
            // filter out '' since this would be a regex problem on JS end
1263
            array_filter(File::config()->get('allowed_extensions'))
1264
        );
1265
1266
        return $upload;
1267
    }
1268
1269
    /**
1270
     * Get response for successfully updated record
1271
     *
1272
     * @param File $record
1273
     * @param Form $form
1274
     * @return HTTPResponse
1275
     */
1276
    protected function getRecordUpdatedResponse($record, $form)
1277
    {
1278
        // Return the record data in the same response as the schema to save a postback
1279
        $schemaData = ['record' => $this->getObjectFromData($record)];
1280
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->ID);
1281
        return $this->getSchemaResponse($schemaId, $form, null, $schemaData);
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 null.

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...
1282
    }
1283
}
1284