Completed
Pull Request — master (#306)
by Will
02:03
created

AssetAdmin::getClientConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 54
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 54
rs 9.6716
cc 1
eloc 40
nc 1
nop 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
894
        $schemaData['record'] = $this->getObjectFromData($record);
895
        $response = new HTTPResponse(Convert::raw2json($schemaData));
896
        $response->addHeader('Content-Type', 'application/json');
897
        return $response;
898
    }
899
900
    public function unpublish($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
901
    {
902 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...
903
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
904
                ->addHeader('Content-Type', 'application/json');
905
        }
906
907
        $id = (int) $data['ID'];
908
        /** @var File $record */
909
        $record = $this->getList()->filter('ID', $id)->first();
910
911
        if (!$record) {
912
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
913
                ->addHeader('Content-Type', 'application/json');
914
        }
915
916
        if (!$record->canUnpublish()) {
917
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
918
                ->addHeader('Content-Type', 'application/json');
919
        }
920
921
        $record->doUnpublish();
922
923
        // Return the record data in the same response as the schema to save a postback
924
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
925
        $schemaData = $this->getSchemaForForm($this->getFileEditForm($id), $schemaId);
0 ignored issues
show
Bug introduced by
It seems like $this->getFileEditForm($id) can be null; however, getSchemaForForm() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
926
        $schemaData['record'] = $this->getObjectFromData($record);
927
        $response = new HTTPResponse(Convert::raw2json($schemaData));
928
        $response->addHeader('Content-Type', 'application/json');
929
        return $response;
930
    }
931
932
    /**
933
     * @param File $file
934
     *
935
     * @return array
936
     */
937
    protected function getObjectFromData(File $file)
938
    {
939
        $object = array(
940
            'id' => $file->ID,
941
            'created' => $file->Created,
942
            'lastUpdated' => $file->LastEdited,
943
            'owner' => null,
944
            'parent' => null,
945
            'title' => $file->Title,
946
            'exists' => $file->exists(), // Broken file check
947
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
948
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
949
            'name' => $file->Name,
950
            'filename' => $file->Filename,
951
            '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...
952
            'size' => $file->Size,
953
            'url' => $file->AbsoluteURL,
954
            'published' => $file->isPublished(),
955
            'modified' => $file->isModifiedOnDraft(),
956
            'draft' => $file->isOnDraftOnly(),
957
            'canEdit' => $file->canEdit(),
958
            'canDelete' => $file->canArchive(),
959
        );
960
961
        /** @var Member $owner */
962
        $owner = $file->Owner();
963
964
        if ($owner) {
965
            $object['owner'] = array(
966
                'id' => $owner->ID,
967
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
968
            );
969
        }
970
971
        /** @var Folder $parent */
972
        $parent = $file->Parent();
973
974
        if ($parent) {
975
            $object['parent'] = array(
976
                'id' => $parent->ID,
977
                'title' => $parent->Title,
978
                'filename' => $parent->Filename,
979
            );
980
        }
981
982
        /** @var File $file */
983
        if ($file->getIsImage()) {
984
            $width = (int)Config::inst()->get(self::class, 'thumbnail_width');
985
            $height = (int)Config::inst()->get(self::class, 'thumbnail_height');
986
987
            $thumbnail = $file->FitMax($width, $height);
988
            if ($thumbnail && $thumbnail->exists()) {
989
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
990
            }
991
            $object['dimensions']['width'] = $file->Width;
992
            $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...
993
        }
994
995
        return $object;
996
    }
997
998
999
    /**
1000
     * Returns the files and subfolders contained in the currently selected folder,
1001
     * defaulting to the root node. Doubles as search results, if any search parameters
1002
     * are set through {@link SearchForm()}.
1003
     *
1004
     * @param array $params Unsanitised request parameters
1005
     * @return DataList
1006
     */
1007
    protected function getList($params = array())
1008
    {
1009
        $context = $this->getSearchContext();
1010
1011
        // Overwrite name filter to search both Name and Title attributes
1012
        $context->removeFilterByName('Name');
1013
1014
        // Lazy loaded list. Allows adding new filters through SearchContext.
1015
        /** @var DataList $list */
1016
        $list = $context->getResults($params);
1017
1018
        // Re-add previously removed "Name" filter as combined filter
1019
        // TODO Replace with composite SearchFilter once that API exists
1020
        if(!empty($params['Name'])) {
1021
            $list = $list->filterAny(array(
1022
                'Name:PartialMatch' => $params['Name'],
1023
                'Title:PartialMatch' => $params['Name']
1024
            ));
1025
        }
1026
1027
        // Optionally limit search to a folder (non-recursive)
1028
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1029
            $list = $list->filter('ParentID', $params['ParentID']);
1030
        }
1031
1032
        // Date filtering
1033 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...
1034
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1035
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1036
        }
1037 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...
1038
            $toDate = new DateField(null, null, $params['CreatedTo']);
1039
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1040
        }
1041
1042
        // Categories
1043
        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...
1044
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1045
            $list = $list->filter('Name:PartialMatch', $extensions);
1046
        }
1047
1048
        // Sort folders first
1049
        $list = $list->sort(
1050
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1051
        );
1052
1053
        // Pagination
1054
        if (isset($filters['page']) && isset($filters['limit'])) {
1055
            $page = $filters['page'];
1056
            $limit = $filters['limit'];
1057
            $offset = ($page - 1) * $limit;
1058
            $list = $list->limit($limit, $offset);
1059
        }
1060
1061
        // Access checks
1062
        $list = $list->filterByCallback(function(File $file) {
1063
            return $file->canView();
1064
        });
1065
1066
        return $list;
1067
    }
1068
1069
    /**
1070
     * Action handler for adding pages to a campaign
1071
     *
1072
     * @param array $data
1073
     * @param Form $form
1074
     * @return DBHTMLText|HTTPResponse
1075
     */
1076
    public function addtocampaign($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1077
    {
1078
        $id = $data['ID'];
1079
        $record = $this->getList()->byID($id);
1080
1081
        $handler = AddToCampaignHandler::create($this, $record);
1082
        $results = $handler->addToCampaign($record, $data['Campaign']);
1083
        if (!is_null($results)) {
1084
            $request = $this->getRequest();
1085
            if($request->getHeader('X-Formschema-Request')) {
1086
                $data = $this->getSchemaForForm($handler->Form($record));
1087
                $data['message'] = $results;
1088
1089
                $response = new HTTPResponse(Convert::raw2json($data));
1090
                $response->addHeader('Content-Type', 'application/json');
1091
                return $response;
1092
            }
1093
            return $results;
1094
        }
1095
    }
1096
1097
    /**
1098
     * Url handler for add to campaign form
1099
     *
1100
     * @param HTTPRequest $request
1101
     * @return Form
1102
     */
1103
    public function AddToCampaignForm($request)
1104
    {
1105
        // Get ID either from posted back value, or url parameter
1106
        $id = $request->param('ID') ?: $request->postVar('ID');
1107
        return $this->getAddToCampaignForm($id);
1108
    }
1109
1110
    /**
1111
     * @param int $id
1112
     * @return Form
1113
     */
1114
    public function getAddToCampaignForm($id)
1115
    {
1116
        // Get record-specific fields
1117
        $record = $this->getList()->byID($id);
1118
1119
        if (!$record) {
1120
            $this->httpError(404, _t(
1121
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1122
                'That {Type} couldn\'t be found',
1123
                '',
1124
                ['Type' => File::singleton()->i18n_singular_name()]
1125
            ));
1126
            return null;
1127
        }
1128 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...
1129
            $this->httpError(403, _t(
1130
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1131
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1132
                '',
1133
                ['ObjectTitle' => $record->i18n_singular_name()]
1134
            ));
1135
            return null;
1136
        }
1137
1138
        $handler = AddToCampaignHandler::create($this, $record);
1139
        $form = $handler->Form($record);
1140
1141 View Code Duplication
        $form->setValidationResponseCallback(function() 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...
1142
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1143
            return $this->getSchemaResponse($form, $schemaId);
1144
        });
1145
1146
        return $form;
1147
    }
1148
1149
    /**
1150
     * @return Upload
1151
     */
1152
    protected function getUpload()
1153
    {
1154
        $upload = Upload::create();
1155
        $upload->getValidator()->setAllowedExtensions(
1156
            // filter out '' since this would be a regex problem on JS end
1157
            array_filter(File::config()->get('allowed_extensions'))
1158
        );
1159
1160
        return $upload;
1161
    }
1162
}
1163