Completed
Pull Request — master (#306)
by Will
01:42
created

AssetAdmin::getFileHistoryForm()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 45
Code Lines 24

Duplication

Lines 14
Ratio 31.11 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 14
loc 45
rs 8.439
cc 5
eloc 24
nc 4
nop 1
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
     * @config
90
     *
91
     * @var int
92
     */
93
    private static $max_history_entries = 100;
0 ignored issues
show
Unused Code introduced by
The property $max_history_entries is not used and could be removed.

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

Loading history...
94
95
    /**
96
     * @var array
97
     */
98
    private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

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

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