Completed
Pull Request — master (#306)
by Will
01:37
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_ago' => $version->dbObject('LastEdited')->Ago(),
491
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
492
                    'status' => ($version->WasPublished) ? _t('File.PUBLISHED', 'Published') : '',
493
                    'author' => ($author)
494
                        ? $author->Name
495
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UNKNOWN', "Unknown"),
496
                    'summary' => ($summary) ? $summary : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NOSUMMARY', "No summary available")
497
                );
498
            }
499
        }
500
501
        return
502
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
503
504
    }
505
506
507
    /**
508
     * Creates a single folder, within an optional parent folder.
509
     *
510
     * @param HTTPRequest $request
511
     * @return HTTPRequest|HTTPResponse
512
     */
513
    public function apiCreateFolder(HTTPRequest $request)
514
    {
515
        $data = $request->postVars();
516
517
        $class = 'SilverStripe\\Assets\\Folder';
518
519
        // CSRF check
520
        $token = SecurityToken::inst();
521 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...
522
            return new HTTPResponse(null, 400);
523
        }
524
525
        // check addchildren permissions
526
        /** @var Folder $parentRecord */
527
        $parentRecord = null;
528
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
529
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
530
        }
531
        $data['Parent'] = $parentRecord;
532
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
533
534
        // Build filename
535
        $baseFilename = isset($data['Name'])
536
            ? basename($data['Name'])
537
            : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NEWFOLDER', "NewFolder");
538
539
        if ($parentRecord && $parentRecord->ID) {
540
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
541
        }
542
543
        // Ensure name is unique
544
        $nameGenerator = $this->getNameGenerator($baseFilename);
545
        $filename = null;
546
        foreach ($nameGenerator as $filename) {
547
            if (! File::find($filename)) {
548
                break;
549
            }
550
        }
551
        $data['Name'] = basename($filename);
552
553
        // Create record
554
        /** @var Folder $record */
555
        $record = Injector::inst()->create($class);
556
557
        // check create permissions
558
        if (!$record->canCreate(null, $data)) {
559
            return (new HTTPResponse(null, 403))
560
                ->addHeader('Content-Type', 'application/json');
561
        }
562
563
        $record->ParentID = $data['ParentID'];
564
        $record->Name = $record->Title = basename($data['Name']);
565
        $record->write();
566
567
        $result = $this->getObjectFromData($record);
568
569
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
570
    }
571
572
    /**
573
     * Redirects 3.x style detail links to new 4.x style routing.
574
     *
575
     * @param HTTPRequest $request
576
     */
577
    public function legacyRedirectForEditView($request)
578
    {
579
        $fileID = $request->param('FileID');
580
        /** @var File $file */
581
        $file = File::get()->byID($fileID);
582
        $link = $this->getFileEditLink($file) ?: $this->Link();
583
        $this->redirect($link);
584
    }
585
586
    /**
587
     * Given a file return the CMS link to edit it
588
     *
589
     * @param File $file
590
     * @return string
591
     */
592
    public function getFileEditLink($file)
593
    {
594
        if(!$file || !$file->isInDB()) {
595
            return null;
596
        }
597
598
        return Controller::join_links(
599
            $this->Link('show'),
600
            $file->ParentID,
601
            'edit',
602
            $file->ID
603
        );
604
    }
605
606
    /**
607
     * Get the search context from {@link File}, used to create the search form
608
     * as well as power the /search API endpoint.
609
     *
610
     * @return SearchContext
611
     */
612
    public function getSearchContext()
613
    {
614
        $context = File::singleton()->getDefaultSearchContext();
615
616
        // Customize fields
617
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
618
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
619
        ->setConfig('showcalendar', true);
620
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
621
        ->setConfig('showcalendar', true);
622
        $dateGroup = FieldGroup::create(
623
            $dateHeader,
624
            $dateFrom,
625
            $dateTo
626
        );
627
        $context->addField($dateGroup);
628
        /** @skipUpgrade */
629
        $appCategories = array(
630
            'archive' => _t(
631
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryArchive',
632
                'Archive'
633
            ),
634
            'audio' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryAudio', 'Audio'),
635
            'document' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryDocument', 'Document'),
636
            'flash' => _t(
637
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryFlash',
638
                'Flash',
639
                'The fileformat'
640
            ),
641
            'image' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryImage', 'Image'),
642
            'video' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryVideo', 'Video'),
643
        );
644
        $context->addField(
645
            $typeDropdown = new DropdownField(
646
                'AppCategory',
647
                _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.Filetype', 'File type'),
648
                $appCategories
649
            )
650
        );
651
652
        $typeDropdown->setEmptyString(' ');
653
654
        $currentfolderLabel = _t(
655
            'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CurrentFolderOnly',
656
            'Limit to current folder?'
657
        );
658
        $context->addField(
659
            new CheckboxField('CurrentFolderOnly', $currentfolderLabel)
660
        );
661
        $context->getFields()->removeByName('Title');
662
663
        return $context;
664
    }
665
666
    /**
667
     * Get an asset renamer for the given filename.
668
     *
669
     * @param  string             $filename Path name
670
     * @return AssetNameGenerator
671
     */
672
    protected function getNameGenerator($filename)
673
    {
674
        return Injector::inst()
675
            ->createWithArgs('AssetNameGenerator', array($filename));
676
    }
677
678
    /**
679
     * @todo Implement on client
680
     *
681
     * @param bool $unlinked
682
     * @return ArrayList
683
     */
684
    public function breadcrumbs($unlinked = false)
685
    {
686
        return null;
687
    }
688
689
690
    /**
691
     * Don't include class namespace in auto-generated CSS class
692
     */
693
    public function baseCSSClasses()
694
    {
695
        return 'AssetAdmin LeftAndMain';
696
    }
697
698
    public function providePermissions()
699
    {
700
        return array(
701
            "CMS_ACCESS_AssetAdmin" => array(
702
                '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...
703
                    'title' => static::menu_title()
704
                )),
705
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
706
            )
707
        );
708
    }
709
710
    /**
711
     * Build a form scaffolder for this model
712
     *
713
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
714
     *
715
     * @param File $file
716
     * @return FormFactory
717
     */
718
    public function getFormFactory(File $file)
719
    {
720
        // Get service name based on file class
721
        $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...
722
        if ($file instanceof Folder) {
723
            $name = FolderFormFactory::class;
724
        } elseif ($file instanceof Image) {
725
            $name = ImageFormFactory::class;
726
        } else {
727
            $name = FileFormFactory::class;
728
        }
729
        return Injector::inst()->get($name);
730
    }
731
732
    /**
733
     * The form is used to generate a form schema,
734
     * as well as an intermediary object to process data through API endpoints.
735
     * Since it's used directly on API endpoints, it does not have any form actions.
736
     * It handles both {@link File} and {@link Folder} records.
737
     *
738
     * @param int $id
739
     * @return Form
740
     */
741
    public function getFileEditForm($id)
742
    {
743
        /** @var File $file */
744
        $file = $this->getList()->byID($id);
745
746 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...
747
            $this->httpError(403, _t(
748
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
749
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
750
                '',
751
                ['ObjectTitle' => $file->i18n_singular_name()]
752
            ));
753
            return null;
754
        }
755
756
        $scaffolder = $this->getFormFactory($file);
757
        $form = $scaffolder->getForm($this, 'FileEditForm', [
758
            'Record' => $file
759
        ]);
760
761
        // Configure form to respond to validation errors with form schema
762
        // if requested via react.
763 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...
764
            $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $file->exists() ? $file->ID : '');
765
            return $this->getSchemaResponse($form, $schemaId);
766
        });
767
768
        return $form;
769
    }
770
771
    /**
772
     * Get file edit form
773
     *
774
     * @return Form
775
     */
776 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...
777
    {
778
        // Get ID either from posted back value, or url parameter
779
        $request = $this->getRequest();
780
        $id = $request->param('ID') ?: $request->postVar('ID');
781
        return $this->getFileEditForm($id);
782
    }
783
784
    /**
785
     *
786
     */
787
    public function getFileHistoryForm($id)
788
    {
789
        /** @var File $file */
790
        $file = $this->getList()->byID($id);
791
792
        $request = $this->getRequest();
793
794 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...
795
            $this->httpError(403, _t(
796
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
797
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
798
                '',
799
                ['ObjectTitle' => $file->i18n_singular_name()]
800
            ));
801
            return null;
802
        }
803
804
        $versionId = $request->param('OtherItemID');
805
        $version = Versioned::get_version($this->getList()->dataClass(), $id, $versionId);
806
807
        if(!$version) {
808
            return $this->httpError(404);
809
        }
810
811
        if(!$version->canView()) {
812
            return $this->httpError(403);
813
        }
814
815
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
816
        $form = $scaffolder->getForm($this, 'FileHistoryForm', [
817
            'Record' => $version
818
        ]);
819
820
        // Configure form to respond to validation errors with form schema
821
        // if requested via react.
822 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...
823
            $schemaId = Controller::join_links($this->Link('schema/FileHistoryForm'), $file->exists() ? $file->ID : '');
824
825
            return $this->getSchemaResponse($form, $schemaId);
826
        });
827
828
        $form->makeReadonly();
829
830
        return $form;
831
    }
832
833
    /**
834
     * Get file history form
835
     *
836
     * @return Form
837
     */
838 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...
839
    {
840
        $request = $this->getRequest();
841
        $id = $request->param('ID') ?: $request->postVar('ID');
842
        $form = $this->getFileHistoryForm($id);
843
844
        return $form;
845
    }
846
847
    /**
848
     * @param array $data
849
     * @param Form $form
850
     * @return HTTPResponse
851
     */
852
    public function save($data, $form)
853
    {
854
        return $this->saveOrPublish($data, $form, false);
855
    }
856
857
858
    /**
859
     * @param array $data
860
     * @param Form $form
861
     * @return HTTPResponse
862
     */
863
    public function publish($data, $form)
864
    {
865
        return $this->saveOrPublish($data, $form, true);
866
    }
867
868
    /**
869
     * Update thisrecord
870
     *
871
     * @param array $data
872
     * @param Form $form
873
     * @param bool $doPublish
874
     * @return HTTPResponse
875
     */
876
    protected function saveOrPublish($data, $form, $doPublish = false)
877
    {
878 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...
879
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
880
                ->addHeader('Content-Type', 'application/json');
881
        }
882
883
        $id = (int) $data['ID'];
884
        /** @var File $record */
885
        $record = $this->getList()->filter('ID', $id)->first();
886
887 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...
888
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
889
                ->addHeader('Content-Type', 'application/json');
890
        }
891
892
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
893
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
894
                ->addHeader('Content-Type', 'application/json');
895
        }
896
897
        $form->saveInto($record);
898
        $record->write();
899
900
        // Publish this record and owned objects
901
        if ($doPublish) {
902
            $record->publishRecursive();
903
        }
904
905
        // Return the record data in the same response as the schema to save a postback
906
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
907
        $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...
908
        $schemaData['record'] = $this->getObjectFromData($record);
909
        $response = new HTTPResponse(Convert::raw2json($schemaData));
910
        $response->addHeader('Content-Type', 'application/json');
911
        return $response;
912
    }
913
914
    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...
915
    {
916 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...
917
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
918
                ->addHeader('Content-Type', 'application/json');
919
        }
920
921
        $id = (int) $data['ID'];
922
        /** @var File $record */
923
        $record = $this->getList()->filter('ID', $id)->first();
924
925
        if (!$record) {
926
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
927
                ->addHeader('Content-Type', 'application/json');
928
        }
929
930
        if (!$record->canUnpublish()) {
931
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
932
                ->addHeader('Content-Type', 'application/json');
933
        }
934
935
        $record->doUnpublish();
936
937
        // Return the record data in the same response as the schema to save a postback
938
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
939
        $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...
940
        $schemaData['record'] = $this->getObjectFromData($record);
941
        $response = new HTTPResponse(Convert::raw2json($schemaData));
942
        $response->addHeader('Content-Type', 'application/json');
943
        return $response;
944
    }
945
946
    /**
947
     * @param File $file
948
     *
949
     * @return array
950
     */
951
    protected function getObjectFromData(File $file)
952
    {
953
        $object = array(
954
            'id' => $file->ID,
955
            'created' => $file->Created,
956
            'lastUpdated' => $file->LastEdited,
957
            'owner' => null,
958
            'parent' => null,
959
            'title' => $file->Title,
960
            'exists' => $file->exists(), // Broken file check
961
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
962
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
963
            'name' => $file->Name,
964
            'filename' => $file->Filename,
965
            '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...
966
            'size' => $file->Size,
967
            'url' => $file->AbsoluteURL,
968
            'published' => $file->isPublished(),
969
            'modified' => $file->isModifiedOnDraft(),
970
            'draft' => $file->isOnDraftOnly(),
971
            'canEdit' => $file->canEdit(),
972
            'canDelete' => $file->canArchive(),
973
        );
974
975
        /** @var Member $owner */
976
        $owner = $file->Owner();
977
978
        if ($owner) {
979
            $object['owner'] = array(
980
                'id' => $owner->ID,
981
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
982
            );
983
        }
984
985
        /** @var Folder $parent */
986
        $parent = $file->Parent();
987
988
        if ($parent) {
989
            $object['parent'] = array(
990
                'id' => $parent->ID,
991
                'title' => $parent->Title,
992
                'filename' => $parent->Filename,
993
            );
994
        }
995
996
        /** @var File $file */
997
        if ($file->getIsImage()) {
998
            $width = (int)Config::inst()->get(self::class, 'thumbnail_width');
999
            $height = (int)Config::inst()->get(self::class, 'thumbnail_height');
1000
1001
            $thumbnail = $file->FitMax($width, $height);
1002
            if ($thumbnail && $thumbnail->exists()) {
1003
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1004
            }
1005
            $object['dimensions']['width'] = $file->Width;
1006
            $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...
1007
        }
1008
1009
        return $object;
1010
    }
1011
1012
1013
    /**
1014
     * Returns the files and subfolders contained in the currently selected folder,
1015
     * defaulting to the root node. Doubles as search results, if any search parameters
1016
     * are set through {@link SearchForm()}.
1017
     *
1018
     * @param array $params Unsanitised request parameters
1019
     * @return DataList
1020
     */
1021
    protected function getList($params = array())
1022
    {
1023
        $context = $this->getSearchContext();
1024
1025
        // Overwrite name filter to search both Name and Title attributes
1026
        $context->removeFilterByName('Name');
1027
1028
        // Lazy loaded list. Allows adding new filters through SearchContext.
1029
        /** @var DataList $list */
1030
        $list = $context->getResults($params);
1031
1032
        // Re-add previously removed "Name" filter as combined filter
1033
        // TODO Replace with composite SearchFilter once that API exists
1034
        if(!empty($params['Name'])) {
1035
            $list = $list->filterAny(array(
1036
                'Name:PartialMatch' => $params['Name'],
1037
                'Title:PartialMatch' => $params['Name']
1038
            ));
1039
        }
1040
1041
        // Optionally limit search to a folder (non-recursive)
1042
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1043
            $list = $list->filter('ParentID', $params['ParentID']);
1044
        }
1045
1046
        // Date filtering
1047 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...
1048
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1049
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1050
        }
1051 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...
1052
            $toDate = new DateField(null, null, $params['CreatedTo']);
1053
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1054
        }
1055
1056
        // Categories
1057
        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...
1058
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1059
            $list = $list->filter('Name:PartialMatch', $extensions);
1060
        }
1061
1062
        // Sort folders first
1063
        $list = $list->sort(
1064
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1065
        );
1066
1067
        // Pagination
1068
        if (isset($filters['page']) && isset($filters['limit'])) {
1069
            $page = $filters['page'];
1070
            $limit = $filters['limit'];
1071
            $offset = ($page - 1) * $limit;
1072
            $list = $list->limit($limit, $offset);
1073
        }
1074
1075
        // Access checks
1076
        $list = $list->filterByCallback(function(File $file) {
1077
            return $file->canView();
1078
        });
1079
1080
        return $list;
1081
    }
1082
1083
    /**
1084
     * Action handler for adding pages to a campaign
1085
     *
1086
     * @param array $data
1087
     * @param Form $form
1088
     * @return DBHTMLText|HTTPResponse
1089
     */
1090
    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...
1091
    {
1092
        $id = $data['ID'];
1093
        $record = $this->getList()->byID($id);
1094
1095
        $handler = AddToCampaignHandler::create($this, $record);
1096
        $results = $handler->addToCampaign($record, $data['Campaign']);
1097
        if (!is_null($results)) {
1098
            $request = $this->getRequest();
1099
            if($request->getHeader('X-Formschema-Request')) {
1100
                $data = $this->getSchemaForForm($handler->Form($record));
1101
                $data['message'] = $results;
1102
1103
                $response = new HTTPResponse(Convert::raw2json($data));
1104
                $response->addHeader('Content-Type', 'application/json');
1105
                return $response;
1106
            }
1107
            return $results;
1108
        }
1109
    }
1110
1111
    /**
1112
     * Url handler for add to campaign form
1113
     *
1114
     * @param HTTPRequest $request
1115
     * @return Form
1116
     */
1117
    public function AddToCampaignForm($request)
1118
    {
1119
        // Get ID either from posted back value, or url parameter
1120
        $id = $request->param('ID') ?: $request->postVar('ID');
1121
        return $this->getAddToCampaignForm($id);
1122
    }
1123
1124
    /**
1125
     * @param int $id
1126
     * @return Form
1127
     */
1128
    public function getAddToCampaignForm($id)
1129
    {
1130
        // Get record-specific fields
1131
        $record = $this->getList()->byID($id);
1132
1133
        if (!$record) {
1134
            $this->httpError(404, _t(
1135
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1136
                'That {Type} couldn\'t be found',
1137
                '',
1138
                ['Type' => File::singleton()->i18n_singular_name()]
1139
            ));
1140
            return null;
1141
        }
1142 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...
1143
            $this->httpError(403, _t(
1144
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1145
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1146
                '',
1147
                ['ObjectTitle' => $record->i18n_singular_name()]
1148
            ));
1149
            return null;
1150
        }
1151
1152
        $handler = AddToCampaignHandler::create($this, $record);
1153
        $form = $handler->Form($record);
1154
1155 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...
1156
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1157
            return $this->getSchemaResponse($form, $schemaId);
1158
        });
1159
1160
        return $form;
1161
    }
1162
1163
    /**
1164
     * @return Upload
1165
     */
1166
    protected function getUpload()
1167
    {
1168
        $upload = Upload::create();
1169
        $upload->getValidator()->setAllowedExtensions(
1170
            // filter out '' since this would be a regex problem on JS end
1171
            array_filter(File::config()->get('allowed_extensions'))
1172
        );
1173
1174
        return $upload;
1175
    }
1176
}
1177