Completed
Pull Request — master (#330)
by Damian
01:54
created

AssetAdmin::getFileEditLink()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 3
eloc 8
nc 2
nop 1
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\FileSearchFormFactory;
12
use SilverStripe\AssetAdmin\Forms\UploadField;
13
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
14
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
15
use SilverStripe\AssetAdmin\Forms\FileHistoryFormFactory;
16
use SilverStripe\AssetAdmin\Forms\ImageFormFactory;
17
use SilverStripe\Assets\File;
18
use SilverStripe\Assets\Folder;
19
use SilverStripe\Assets\Image;
20
use SilverStripe\Assets\Storage\AssetNameGenerator;
21
use SilverStripe\Assets\Upload;
22
use SilverStripe\Control\Controller;
23
use SilverStripe\Control\HTTPRequest;
24
use SilverStripe\Control\HTTPResponse;
25
use SilverStripe\Core\Injector\Injector;
26
use SilverStripe\Forms\CheckboxField;
27
use SilverStripe\Forms\DateField;
28
use SilverStripe\Forms\DropdownField;
29
use SilverStripe\Forms\FieldGroup;
30
use SilverStripe\Forms\Form;
31
use SilverStripe\Forms\FormFactory;
32
use SilverStripe\Forms\HeaderField;
33
use SilverStripe\ORM\ArrayList;
34
use SilverStripe\ORM\DataList;
35
use SilverStripe\ORM\DataObject;
36
use SilverStripe\ORM\FieldType\DBHTMLText;
37
use SilverStripe\ORM\Search\SearchContext;
38
use SilverStripe\ORM\ValidationResult;
39
use SilverStripe\Security\Member;
40
use SilverStripe\Security\PermissionProvider;
41
use SilverStripe\Security\SecurityToken;
42
use SilverStripe\View\Requirements;
43
use SilverStripe\ORM\Versioning\Versioned;
44
45
/**
46
 * AssetAdmin is the 'file store' section of the CMS.
47
 * It provides an interface for manipulating the File and Folder objects in the system.
48
 */
49
class AssetAdmin extends LeftAndMain implements PermissionProvider
50
{
51
    private static $url_segment = 'assets';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_segment is not used and could be removed.

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

Loading history...
52
53
    private static $url_rule = '/$Action/$ID';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_rule is not used and could be removed.

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

Loading history...
54
55
    private static $menu_title = 'Files';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $menu_title is not used and could be removed.

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

Loading history...
56
57
    private static $tree_class = 'SilverStripe\\Assets\\Folder';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $tree_class is not used and could be removed.

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

Loading history...
58
59
    private static $url_handlers = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_handlers is not used and could be removed.

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

Loading history...
60
        // Legacy redirect for SS3-style detail view
61
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
62
        // Pass all URLs to the index, for React to unpack
63
        'show/$FolderID/edit/$FileID' => 'index',
64
        // API access points with structured data
65
        'POST api/createFolder' => 'apiCreateFolder',
66
        'POST api/createFile' => 'apiCreateFile',
67
        'GET api/readFolder' => 'apiReadFolder',
68
        'PUT api/updateFolder' => 'apiUpdateFolder',
69
        'DELETE api/delete' => 'apiDelete',
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
        'fileEditForm',
107
        'fileHistoryForm',
108
        'addToCampaignForm',
109
        'fileInsertForm',
110
        'schema',
111
        'fileSelectForm',
112
        'fileSearchForm',
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
            'updateFolderEndpoint' => [
156
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
157
                'method' => 'put',
158
                'payloadFormat' => 'urlencoded',
159
            ],
160
            'deleteEndpoint' => [
161
                'url' => Controller::join_links($baseLink, 'api/delete'),
162
                'method' => 'delete',
163
                'payloadFormat' => 'urlencoded',
164
            ],
165
            'historyEndpoint' => [
166
                'url' => Controller::join_links($baseLink, 'api/history'),
167
                'method' => 'get',
168
                'responseFormat' => 'json'
169
            ],
170
            'limit' => $this->config()->page_length,
171
            'form' => [
172
                'fileEditForm' => [
173
                    'schemaUrl' => $this->Link('schema/fileEditForm')
174
                ],
175
                'fileInsertForm' => [
176
                    'schemaUrl' => $this->Link('schema/fileInsertForm')
177
                ],
178
                'fileSelectForm' => [
179
                    'schemaUrl' => $this->Link('schema/fileSelectForm')
180
                ],
181
                'addToCampaignForm' => [
182
                    'schemaUrl' => $this->Link('schema/addToCampaignForm')
183
                ],
184
                'fileHistoryForm' => [
185
                    'schemaUrl' => $this->Link('schema/fileHistoryForm')
186
                ],
187
                'fileSearchForm' => [
188
                    'schemaUrl' => $this->Link('schema/fileSearchForm')
189
                ],
190
            ],
191
        ]);
192
    }
193
194
    /**
195
     * Fetches a collection of files by ParentID.
196
     *
197
     * @param HTTPRequest $request
198
     * @return HTTPResponse
199
     */
200
    public function apiReadFolder(HTTPRequest $request)
201
    {
202
        $params = $request->requestVars();
203
        $items = array();
204
        $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...
205
        $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...
206
        $query = isset($params['search']) ? $params['search'] : [];
207
208
        if (!isset($params['id']) && !strlen($params['id'])) {
209
            $this->httpError(400);
210
        }
211
212
        $folderID = (int)$params['id'];
213
        /** @var Folder $folder */
214
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
215
216
        if (!$folder) {
217
            $this->httpError(400);
218
        }
219
220
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
221
        if (empty($query['AllFolders'])) {
222
            $query['ParentID'] = $folderID;
223
        } else {
224
            unset($query['AllFolders']);
225
        }
226
        $files = $this->getList($query);
227
228
        if ($files) {
229
            /** @var File $file */
230
            foreach ($files as $file) {
231
                if (!$file->canView()) {
232
                    continue;
233
                }
234
235
                $items[] = $this->getObjectFromData($file);
236
            }
237
        }
238
239
        // Build parents (for breadcrumbs)
240
        $parents = [];
241
        $next = $folder->Parent();
242
        while ($next && $next->exists()) {
243
            array_unshift($parents, [
244
                'id' => $next->ID,
245
                'title' => $next->getTitle(),
246
                'filename' => $next->getFilename(),
247
            ]);
248
            if ($next->ParentID) {
249
                $next = $next->Parent();
250
            } else {
251
                break;
252
            }
253
        }
254
255
        $column = 'title';
256
        $direction = 'asc';
257
        if (isset($params['sort'])) {
258
            list($column, $direction) = explode(',', $params['sort']);
259
        }
260
        $multiplier = ($direction === 'asc') ? 1 : -1;
261
262
        usort($items, function ($a, $b) use ($column, $multiplier) {
263
            if (!isset($a[$column]) || !isset($b[$column])) {
264
                return 0;
265
            }
266
            if ($a['type'] === 'folder' && $b['type'] !== 'folder') {
267
                return -1;
268
            }
269
            if ($b['type'] === 'folder' && $a['type'] !== 'folder') {
270
                return 1;
271
            }
272
            $numeric = (is_numeric($a[$column]) && is_numeric($b[$column]));
273
            $fieldA = ($numeric) ? floatval($a[$column]) : strtolower($a[$column]);
274
            $fieldB = ($numeric) ? floatval($b[$column]) : strtolower($b[$column]);
275
276
            if ($fieldA < $fieldB) {
277
                return $multiplier * -1;
278
            }
279
280
            if ($fieldA > $fieldB) {
281
                return $multiplier;
282
            }
283
284
            return 0;
285
        });
286
287
        $page = (isset($params['page'])) ? $params['page'] : 0;
288
        $limit = (isset($params['limit'])) ? $params['limit'] : $this->config()->page_length;
289
        $filteredItems = array_slice($items, $page * $limit, $limit);
290
291
        // Build response
292
        $response = new HTTPResponse();
293
        $response->addHeader('Content-Type', 'application/json');
294
        $response->setBody(json_encode([
295
            'files' => $filteredItems,
296
            'title' => $folder->getTitle(),
297
            'count' => count($items),
298
            'parents' => $parents,
299
            'parent' => $parents ? $parents[count($parents) - 1] : null,
300
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
301
            'folderID' => $folderID,
302
            'canEdit' => $folder->canEdit(),
303
            'canDelete' => $folder->canArchive(),
304
        ]));
305
306
        return $response;
307
    }
308
309
    /**
310
     * @param HTTPRequest $request
311
     *
312
     * @return HTTPResponse
313
     */
314
    public function apiDelete(HTTPRequest $request)
315
    {
316
        parse_str($request->getBody(), $vars);
317
318
        // CSRF check
319
        $token = SecurityToken::inst();
320 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...
321
            return new HTTPResponse(null, 400);
322
        }
323
324 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...
325
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
326
                ->addHeader('Content-Type', 'application/json');
327
        }
328
329
        $fileIds = $vars['ids'];
330
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
331
332 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...
333
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
334
                ->addHeader('Content-Type', 'application/json');
335
        }
336
337
        if (!min(array_map(function (File $file) {
338
            return $file->canArchive();
339
        }, $files))) {
340
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
341
                ->addHeader('Content-Type', 'application/json');
342
        }
343
344
        /** @var File $file */
345
        foreach ($files as $file) {
346
            $file->doArchive();
347
        }
348
349
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
350
            ->addHeader('Content-Type', 'application/json');
351
    }
352
353
    /**
354
     * Creates a single file based on a form-urlencoded upload.
355
     *
356
     * @param HTTPRequest $request
357
     * @return HTTPRequest|HTTPResponse
358
     */
359
    public function apiCreateFile(HTTPRequest $request)
360
    {
361
        $data = $request->postVars();
362
        $upload = $this->getUpload();
363
364
        // CSRF check
365
        $token = SecurityToken::inst();
366 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...
367
            return new HTTPResponse(null, 400);
368
        }
369
370
        // Check parent record
371
        /** @var Folder $parentRecord */
372
        $parentRecord = null;
373
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
374
            $parentRecord = Folder::get()->byID($data['ParentID']);
375
        }
376
        $data['Parent'] = $parentRecord;
377
378
        $tmpFile = $request->postVar('Upload');
379
        if (!$upload->validate($tmpFile)) {
380
            $result = ['message' => null];
381
            $errors = $upload->getErrors();
382
            if ($message = array_shift($errors)) {
383
                $result['message'] = [
384
                    'type' => 'error',
385
                    'value' => $message,
386
                ];
387
            }
388
            return (new HTTPResponse(json_encode($result), 400))
389
                ->addHeader('Content-Type', 'application/json');
390
        }
391
392
        // TODO Allow batch uploads
393
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
394
        /** @var File $file */
395
        $file = Injector::inst()->create($fileClass);
396
397
        // check canCreate permissions
398 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...
399
            $result = ['message' => [
400
                'type' => 'error',
401
                'value' => _t(
402
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
403
                    'You do not have permission to add files'
404
                )
405
            ]];
406
            return (new HTTPResponse(json_encode($result), 403))
407
                ->addHeader('Content-Type', 'application/json');
408
        }
409
410
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
411 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...
412
            $result = ['message' => [
413
                'type' => 'error',
414
                'value' => _t(
415
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
416
                    'Failed to load file'
417
                )
418
            ]];
419
            return (new HTTPResponse(json_encode($result), 400))
420
                ->addHeader('Content-Type', 'application/json');
421
        }
422
423
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
424
        $file->write();
425
426
        $result = [$this->getObjectFromData($file)];
427
428
        return (new HTTPResponse(json_encode($result)))
429
            ->addHeader('Content-Type', 'application/json');
430
    }
431
432
    /**
433
     * Returns a JSON array for history of a given file ID. Returns a list of all the history.
434
     *
435
     * @param HTTPRequest $request
436
     * @return HTTPResponse
437
     */
438
    public function apiHistory(HTTPRequest $request)
439
    {
440
        // CSRF check not required as the GET request has no side effects.
441
        $fileId = $request->getVar('fileId');
442
443
        if (!$fileId || !is_numeric($fileId)) {
444
            return new HTTPResponse(null, 400);
445
        }
446
447
        $class = File::class;
448
        $file = DataObject::get($class)->byID($fileId);
449
450
        if (!$file) {
451
            return new HTTPResponse(null, 404);
452
        }
453
454
        if (!$file->canView()) {
455
            return new HTTPResponse(null, 403);
456
        }
457
458
        $versions = Versioned::get_all_versions($class, $fileId)
459
            ->limit($this->config()->max_history_entries)
460
            ->sort('Version', 'DESC');
461
462
        $output = array();
463
        $next = array();
464
        $prev = null;
465
466
        // swap the order so we can get the version number to compare against.
467
        // i.e version 3 needs to know version 2 is the previous version
468
        $copy = $versions->map('Version', 'Version')->toArray();
469
        foreach (array_reverse($copy) as $k => $v) {
470
            if ($prev) {
471
                $next[$v] = $prev;
472
            }
473
474
            $prev = $v;
475
        }
476
477
        $_cachedMembers = array();
478
479
        /** @var File $version */
480
        foreach ($versions as $version) {
481
            $author = null;
482
483
            if ($version->AuthorID) {
484
                if (!isset($_cachedMembers[$version->AuthorID])) {
485
                    $_cachedMembers[$version->AuthorID] = DataObject::get(Member::class)
486
                        ->byID($version->AuthorID);
487
                }
488
489
                $author = $_cachedMembers[$version->AuthorID];
490
            }
491
492
            if ($version->canView()) {
493
                if (isset($next[$version->Version])) {
494
                    $summary = $version->humanizedChanges(
495
                        $version->Version,
496
                        $next[$version->Version]
497
                    );
498
499
                    // if no summary returned by humanizedChanges, i.e we cannot work out what changed, just show a
500
                    // generic message
501
                    if (!$summary) {
502
                        $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.SAVEDFILE', "Saved file");
503
                    }
504
                } else {
505
                    $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UPLOADEDFILE', "Uploaded file");
506
                }
507
508
                $output[] = array(
509
                    'versionid' => $version->Version,
510
                    'date_ago' => $version->dbObject('LastEdited')->Ago(),
511
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
512
                    'status' => ($version->WasPublished) ? _t('File.PUBLISHED', 'Published') : '',
513
                    'author' => ($author)
514
                        ? $author->Name
515
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UNKNOWN', "Unknown"),
516
                    'summary' => ($summary)
517
                        ? $summary
518
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NOSUMMARY', "No summary available")
519
                );
520
            }
521
        }
522
523
        return
524
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
525
    }
526
527
528
    /**
529
     * Creates a single folder, within an optional parent folder.
530
     *
531
     * @param HTTPRequest $request
532
     * @return HTTPRequest|HTTPResponse
533
     */
534
    public function apiCreateFolder(HTTPRequest $request)
535
    {
536
        $data = $request->postVars();
537
538
        $class = 'SilverStripe\\Assets\\Folder';
539
540
        // CSRF check
541
        $token = SecurityToken::inst();
542 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...
543
            return new HTTPResponse(null, 400);
544
        }
545
546
        // check addchildren permissions
547
        /** @var Folder $parentRecord */
548
        $parentRecord = null;
549
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
550
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
551
        }
552
        $data['Parent'] = $parentRecord;
553
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
554
555
        // Build filename
556
        $baseFilename = isset($data['Name'])
557
            ? basename($data['Name'])
558
            : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NEWFOLDER', "NewFolder");
559
560
        if ($parentRecord && $parentRecord->ID) {
561
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
562
        }
563
564
        // Ensure name is unique
565
        $nameGenerator = $this->getNameGenerator($baseFilename);
566
        $filename = null;
567
        foreach ($nameGenerator as $filename) {
568
            if (! File::find($filename)) {
569
                break;
570
            }
571
        }
572
        $data['Name'] = basename($filename);
573
574
        // Create record
575
        /** @var Folder $record */
576
        $record = Injector::inst()->create($class);
577
578
        // check create permissions
579
        if (!$record->canCreate(null, $data)) {
580
            return (new HTTPResponse(null, 403))
581
                ->addHeader('Content-Type', 'application/json');
582
        }
583
584
        $record->ParentID = $data['ParentID'];
585
        $record->Name = $record->Title = basename($data['Name']);
586
        $record->write();
587
588
        $result = $this->getObjectFromData($record);
589
590
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
591
    }
592
593
    /**
594
     * Redirects 3.x style detail links to new 4.x style routing.
595
     *
596
     * @param HTTPRequest $request
597
     */
598
    public function legacyRedirectForEditView($request)
599
    {
600
        $fileID = $request->param('FileID');
601
        /** @var File $file */
602
        $file = File::get()->byID($fileID);
603
        $link = $this->getFileEditLink($file) ?: $this->Link();
604
        $this->redirect($link);
605
    }
606
607
    /**
608
     * Given a file return the CMS link to edit it
609
     *
610
     * @param File $file
611
     * @return string
612
     */
613
    public function getFileEditLink($file)
614
    {
615
        if (!$file || !$file->isInDB()) {
616
            return null;
617
        }
618
619
        return Controller::join_links(
620
            $this->Link('show'),
621
            $file->ParentID,
622
            'edit',
623
            $file->ID
624
        );
625
    }
626
627
    /**
628
     * Get an asset renamer for the given filename.
629
     *
630
     * @param  string             $filename Path name
631
     * @return AssetNameGenerator
632
     */
633
    protected function getNameGenerator($filename)
634
    {
635
        return Injector::inst()
636
            ->createWithArgs('AssetNameGenerator', array($filename));
637
    }
638
639
    /**
640
     * @todo Implement on client
641
     *
642
     * @param bool $unlinked
643
     * @return ArrayList
644
     */
645
    public function breadcrumbs($unlinked = false)
646
    {
647
        return null;
648
    }
649
650
651
    /**
652
     * Don't include class namespace in auto-generated CSS class
653
     */
654
    public function baseCSSClasses()
655
    {
656
        return 'AssetAdmin LeftAndMain';
657
    }
658
659
    public function providePermissions()
660
    {
661
        return array(
662
            "CMS_ACCESS_AssetAdmin" => array(
663
                '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...
664
                    'title' => static::menu_title()
665
                )),
666
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
667
            )
668
        );
669
    }
670
671
    /**
672
     * Build a form scaffolder for this model
673
     *
674
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
675
     *
676
     * @param File $file
677
     * @return FormFactory
678
     */
679
    public function getFormFactory(File $file)
680
    {
681
        // Get service name based on file class
682
        $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...
683
        if ($file instanceof Folder) {
684
            $name = FolderFormFactory::class;
685
        } elseif ($file instanceof Image) {
686
            $name = ImageFormFactory::class;
687
        } else {
688
            $name = FileFormFactory::class;
689
        }
690
        return Injector::inst()->get($name);
691
    }
692
693
    /**
694
     * The form is used to generate a form schema,
695
     * as well as an intermediary object to process data through API endpoints.
696
     * Since it's used directly on API endpoints, it does not have any form actions.
697
     * It handles both {@link File} and {@link Folder} records.
698
     *
699
     * @param int $id
700
     * @return Form
701
     */
702
    public function getFileEditForm($id)
703
    {
704
        return $this->getAbstractFileForm($id, 'fileEditForm');
705
    }
706
707
    /**
708
     * Get file edit form
709
     *
710
     * @return Form
711
     */
712 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...
713
    {
714
        // Get ID either from posted back value, or url parameter
715
        $request = $this->getRequest();
716
        $id = $request->param('ID') ?: $request->postVar('ID');
717
        return $this->getFileEditForm($id);
718
    }
719
720
    /**
721
     * The form is used to generate a form schema,
722
     * as well as an intermediary object to process data through API endpoints.
723
     * Since it's used directly on API endpoints, it does not have any form actions.
724
     * It handles both {@link File} and {@link Folder} records.
725
     *
726
     * @param int $id
727
     * @return Form
728
     */
729
    public function getFileInsertForm($id)
730
    {
731
        return $this->getAbstractFileForm($id, 'fileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT ]);
732
    }
733
734
    /**
735
     * Get file insert form
736
     *
737
     * @return Form
738
     */
739 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...
740
    {
741
        // Get ID either from posted back value, or url parameter
742
        $request = $this->getRequest();
743
        $id = $request->param('ID') ?: $request->postVar('ID');
744
        return $this->getFileInsertForm($id);
745
    }
746
747
    /**
748
     * Abstract method for generating a form for a file
749
     *
750
     * @param int $id Record ID
751
     * @param string $name Form name
752
     * @param array $context Form context
753
     * @return Form
754
     */
755
    protected function getAbstractFileForm($id, $name, $context = [])
756
    {
757
        /** @var File $file */
758
        $file = $this->getList()->byID($id);
759
760 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...
761
            $this->httpError(403, _t(
762
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
763
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
764
                '',
765
                ['ObjectTitle' => $file->i18n_singular_name()]
766
            ));
767
            return null;
768
        }
769
770
        // Pass to form factory
771
        $augmentedContext = array_merge($context, ['Record' => $file]);
772
        $scaffolder = $this->getFormFactory($file);
773
        $form = $scaffolder->getForm($this, $name, $augmentedContext);
774
775
        // Configure form to respond to validation errors with form schema
776
        // if requested via react.
777
        $form->setValidationResponseCallback(function (ValidationResult $error) use ($form, $id, $name) {
778
            $schemaId = Controller::join_links($this->Link('schema'), $name, $id);
779
            return $this->getSchemaResponse($schemaId, $form, $error);
780
        });
781
782
        return $form;
783
    }
784
785
    /**
786
     * Get form for selecting a file
787
     *
788
     * @return Form
789
     */
790
    public function fileSelectForm()
791
    {
792
        // Get ID either from posted back value, or url parameter
793
        $request = $this->getRequest();
794
        $id = $request->param('ID') ?: $request->postVar('ID');
795
        return $this->getFileSelectForm($id);
796
    }
797
798
    /**
799
     * Get form for selecting a file
800
     *
801
     * @param int $id ID of the record being selected
802
     * @return Form
803
     */
804
    public function getFileSelectForm($id)
805
    {
806
        return $this->getAbstractFileForm($id, 'fileSelectForm', [ 'Type' => AssetFormFactory::TYPE_SELECT ]);
807
    }
808
809
    /**
810
     * @param array $context
811
     * @return Form
812
     * @throws InvalidArgumentException
813
     */
814
    public function getFileHistoryForm($context)
815
    {
816
        // Check context
817
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
818
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
819
        }
820
        $id = $context['RecordID'];
821
        $versionId = $context['RecordVersion'];
822
        if (!$id || !$versionId) {
823
            return $this->httpError(404);
824
        }
825
826
        /** @var File $file */
827
        $file = Versioned::get_version(File::class, $id, $versionId);
828
        if (!$file) {
829
            return $this->httpError(404);
830
        }
831
832 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...
833
            $this->httpError(403, _t(
834
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
835
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
836
                '',
837
                ['ObjectTitle' => $file->i18n_singular_name()]
838
            ));
839
            return null;
840
        }
841
842
        $effectiveContext = array_merge($context, ['Record' => $file]);
843
        /** @var FormFactory $scaffolder */
844
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
845
        $form = $scaffolder->getForm($this, 'fileHistoryForm', $effectiveContext);
846
847
        // Configure form to respond to validation errors with form schema
848
        // if requested via react.
849 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...
850
            $schemaId = Controller::join_links($this->Link('schema/fileHistoryForm'), $id, $versionId);
851
            return $this->getSchemaResponse($schemaId, $form, $errors);
852
        });
853
854
        return $form;
855
    }
856
857
    /**
858
     * Gets a JSON schema representing the current edit form.
859
     *
860
     * WARNING: Experimental API.
861
     *
862
     * @param HTTPRequest $request
863
     * @return HTTPResponse
864
     */
865
    public function schema($request)
866
    {
867
        $formName = $request->param('FormName');
868
        if ($formName !== 'fileHistoryForm') {
869
            return parent::schema($request);
870
        }
871
872
        // Get schema for history form
873
        // @todo Eventually all form scaffolding will be based on context rather than record ID
874
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
875
        $itemID = $request->param('ItemID');
876
        $version = $request->param('OtherItemID');
877
        $form = $this->getFileHistoryForm([
878
            'RecordID' => $itemID,
879
            'RecordVersion' => $version,
880
        ]);
881
882
        // Respond with this schema
883
        $response = $this->getResponse();
884
        $response->addHeader('Content-Type', 'application/json');
885
        $schemaID = $this->getRequest()->getURL();
886
        return $this->getSchemaResponse($schemaID, $form);
887
    }
888
889
    /**
890
     * Get file history form
891
     *
892
     * @return Form
893
     */
894
    public function fileHistoryForm()
895
    {
896
        $request = $this->getRequest();
897
        $id = $request->param('ID') ?: $request->postVar('ID');
898
        $version = $request->param('OtherID') ?: $request->postVar('Version');
899
        $form = $this->getFileHistoryForm([
900
            'RecordID' => $id,
901
            'RecordVersion' => $version,
902
        ]);
903
        return $form;
904
    }
905
906
    /**
907
     * @param array $data
908
     * @param Form $form
909
     * @return HTTPResponse
910
     */
911
    public function save($data, $form)
912
    {
913
        return $this->saveOrPublish($data, $form, false);
914
    }
915
916
    /**
917
     * @param array $data
918
     * @param Form $form
919
     * @return HTTPResponse
920
     */
921
    public function publish($data, $form)
922
    {
923
        return $this->saveOrPublish($data, $form, true);
924
    }
925
926
    /**
927
     * Update thisrecord
928
     *
929
     * @param array $data
930
     * @param Form $form
931
     * @param bool $doPublish
932
     * @return HTTPResponse
933
     */
934
    protected function saveOrPublish($data, $form, $doPublish = false)
935
    {
936 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...
937
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
938
                ->addHeader('Content-Type', 'application/json');
939
        }
940
941
        $id = (int) $data['ID'];
942
        /** @var File $record */
943
        $record = $this->getList()->filter('ID', $id)->first();
944
945 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...
946
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
947
                ->addHeader('Content-Type', 'application/json');
948
        }
949
950
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
951
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
952
                ->addHeader('Content-Type', 'application/json');
953
        }
954
955
        $form->saveInto($record);
956
        $record->write();
957
958
        // Publish this record and owned objects
959
        if ($doPublish) {
960
            $record->publishRecursive();
961
        }
962
963
        // Note: Force return of schema / state in success result
964
        return $this->getRecordUpdatedResponse($record, $form);
965
    }
966
967
    public function unpublish($data, $form)
968
    {
969 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...
970
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
971
                ->addHeader('Content-Type', 'application/json');
972
        }
973
974
        $id = (int) $data['ID'];
975
        /** @var File $record */
976
        $record = $this->getList()->filter('ID', $id)->first();
977
978
        if (!$record) {
979
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
980
                ->addHeader('Content-Type', 'application/json');
981
        }
982
983
        if (!$record->canUnpublish()) {
984
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
985
                ->addHeader('Content-Type', 'application/json');
986
        }
987
988
        $record->doUnpublish();
989
        return $this->getRecordUpdatedResponse($record, $form);
990
    }
991
992
    /**
993
     * @param File $file
994
     *
995
     * @return array
996
     */
997
    public function getObjectFromData(File $file)
998
    {
999
        $object = array(
1000
            'id' => $file->ID,
1001
            'created' => $file->Created,
1002
            'lastUpdated' => $file->LastEdited,
1003
            'owner' => null,
1004
            'parent' => null,
1005
            'title' => $file->Title,
1006
            'exists' => $file->exists(), // Broken file check
1007
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
1008
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
1009
            'name' => $file->Name,
1010
            'filename' => $file->Filename,
1011
            '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...
1012
            'size' => $file->AbsoluteSize,
1013
            'url' => $file->AbsoluteURL,
1014
            'published' => $file->isPublished(),
1015
            'modified' => $file->isModifiedOnDraft(),
1016
            'draft' => $file->isOnDraftOnly(),
1017
            'canEdit' => $file->canEdit(),
1018
            'canDelete' => $file->canArchive(),
1019
        );
1020
1021
        /** @var Member $owner */
1022
        $owner = $file->Owner();
1023
1024
        if ($owner) {
1025
            $object['owner'] = array(
1026
                'id' => $owner->ID,
1027
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1028
            );
1029
        }
1030
1031
        /** @var Folder $parent */
1032
        $parent = $file->Parent();
1033
1034
        if ($parent) {
1035
            $object['parent'] = array(
1036
                'id' => $parent->ID,
1037
                'title' => $parent->Title,
1038
                'filename' => $parent->Filename,
1039
            );
1040
        }
1041
1042
        /** @var File $file */
1043
        if ($file->getIsImage()) {
1044
            // Small thumbnail
1045
            $smallWidth = UploadField::config()->get('thumbnail_width');
1046
            $smallHeight = UploadField::config()->get('thumbnail_height');
1047
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1048
            if ($smallThumbnail && $smallThumbnail->exists()) {
1049
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1050
            }
1051
1052
            // Large thumbnail
1053
            $width = $this->config()->get('thumbnail_width');
1054
            $height = $this->config()->get('thumbnail_height');
1055
            $thumbnail = $file->FitMax($width, $height);
1056
            if ($thumbnail && $thumbnail->exists()) {
1057
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1058
            }
1059
            $object['dimensions']['width'] = $file->Width;
1060
            $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...
1061
        } else {
1062
            $object['thumbnail'] = $file->PreviewLink();
1063
        }
1064
1065
        return $object;
1066
    }
1067
1068
    /**
1069
     * Returns the files and subfolders contained in the currently selected folder,
1070
     * defaulting to the root node. Doubles as search results, if any search parameters
1071
     * are set through {@link SearchForm()}.
1072
     *
1073
     * @param array $params Unsanitised request parameters
1074
     * @return DataList
1075
     */
1076
    protected function getList($params = array())
1077
    {
1078
        /** @var DataList $list */
1079
        $list = File::get();
1080
1081
        // Re-add previously removed "Name" filter as combined filter
1082
        if (!empty($params['Name'])) {
1083
            $list = $list->filterAny(array(
1084
                'Name:PartialMatch' => $params['Name'],
1085
                'Title:PartialMatch' => $params['Name']
1086
            ));
1087
        }
1088
1089
        // Optionally limit search to a folder (non-recursive)
1090
        if (isset($params['ParentID']) && is_numeric($params['ParentID'])) {
1091
            $list = $list->filter('ParentID', $params['ParentID']);
1092
        }
1093
1094
        // Date filtering
1095 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...
1096
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1097
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1098
        }
1099 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...
1100
            $toDate = new DateField(null, null, $params['CreatedTo']);
1101
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1102
        }
1103
1104
        // Categories
1105
        $categories = File::config()->app_categories;
1106
        if (!empty($filters['AppCategory']) && !empty($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...
1107
            $extensions = $categories[$filters['AppCategory']];
1108
            $list = $list->filter('Name:EndsWith', $extensions);
1109
        }
1110
1111
        // Sort folders first
1112
        $list = $list->sort(
1113
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1114
        );
1115
1116
        // Pagination
1117
        if (isset($filters['page']) && isset($filters['limit'])) {
1118
            $page = $filters['page'];
1119
            $limit = $filters['limit'];
1120
            $offset = ($page - 1) * $limit;
1121
            $list = $list->limit($limit, $offset);
1122
        }
1123
1124
        // Access checks
1125
        $list = $list->filterByCallback(function (File $file) {
1126
            return $file->canView();
1127
        });
1128
1129
        return $list;
1130
    }
1131
1132
    /**
1133
     * Action handler for adding pages to a campaign
1134
     *
1135
     * @param array $data
1136
     * @param Form $form
1137
     * @return DBHTMLText|HTTPResponse
1138
     */
1139
    public function addtocampaign($data, $form)
1140
    {
1141
        $id = $data['ID'];
1142
        $record = $this->getList()->byID($id);
1143
1144
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1145
        $results = $handler->addToCampaign($record, $data['Campaign']);
1146
        if (!isset($results)) {
1147
            return null;
1148
        }
1149
1150
        // Send extra "message" data with schema response
1151
        $extraData = ['message' => $results];
1152
        $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1153
        return $this->getSchemaResponse($schemaId, $form, null, $extraData);
1154
    }
1155
1156
    /**
1157
     * Url handler for add to campaign form
1158
     *
1159
     * @param HTTPRequest $request
1160
     * @return Form
1161
     */
1162
    public function addToCampaignForm($request)
1163
    {
1164
        // Get ID either from posted back value, or url parameter
1165
        $id = $request->param('ID') ?: $request->postVar('ID');
1166
        return $this->getAddToCampaignForm($id);
1167
    }
1168
1169
    /**
1170
     * @param int $id
1171
     * @return Form
1172
     */
1173
    public function getAddToCampaignForm($id)
1174
    {
1175
        // Get record-specific fields
1176
        $record = $this->getList()->byID($id);
1177
1178
        if (!$record) {
1179
            $this->httpError(404, _t(
1180
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1181
                'That {Type} couldn\'t be found',
1182
                '',
1183
                ['Type' => File::singleton()->i18n_singular_name()]
1184
            ));
1185
            return null;
1186
        }
1187 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...
1188
            $this->httpError(403, _t(
1189
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1190
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1191
                '',
1192
                ['ObjectTitle' => $record->i18n_singular_name()]
1193
            ));
1194
            return null;
1195
        }
1196
1197
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1198
        $form = $handler->Form($record);
1199
1200 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...
1201
            $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1202
            return $this->getSchemaResponse($schemaId, $form, $errors);
1203
        });
1204
1205
        return $form;
1206
    }
1207
1208
    /**
1209
     * @return Upload
1210
     */
1211
    protected function getUpload()
1212
    {
1213
        $upload = Upload::create();
1214
        $upload->getValidator()->setAllowedExtensions(
1215
            // filter out '' since this would be a regex problem on JS end
1216
            array_filter(File::config()->get('allowed_extensions'))
1217
        );
1218
1219
        return $upload;
1220
    }
1221
1222
    /**
1223
     * Get response for successfully updated record
1224
     *
1225
     * @param File $record
1226
     * @param Form $form
1227
     * @return HTTPResponse
1228
     */
1229
    protected function getRecordUpdatedResponse($record, $form)
1230
    {
1231
        // Return the record data in the same response as the schema to save a postback
1232
        $schemaData = ['record' => $this->getObjectFromData($record)];
1233
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->ID);
1234
        return $this->getSchemaResponse($schemaId, $form, null, $schemaData);
1235
    }
1236
1237
    /**
1238
     * Scaffold a search form.
1239
     * Note: This form does not submit to itself, but rather uses the apiReadFolder endpoint
1240
     * (to be replaced with graphql)
1241
     *
1242
     * @return Form
1243
     */
1244
    public function fileSearchForm()
1245
    {
1246
        $scaffolder = FileSearchFormFactory::singleton();
1247
        return $scaffolder->getForm($this, 'fileSearchForm', []);
1248
    }
1249
1250
    /**
1251
     * Allow search form to be accessible to schema
1252
     *
1253
     * @return Form
1254
     */
1255
    public function getFileSearchform() {
1256
        return $this->fileSearchForm();
1257
    }
1258
}
1259