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

AssetAdmin::FileHistoryForm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 8
loc 8
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
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
        'FileInsertForm',
111
    );
112
113
    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...
114
115
    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...
116
117
    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...
118
119
    /**
120
     * Set up the controller
121
     */
122
    public function init()
123
    {
124
        parent::init();
125
126
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
127
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
128
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
129
130
        CMSBatchActionHandler::register(
131
            'delete',
132
            'SilverStripe\AssetAdmin\BatchAction\DeleteAssets',
133
            'SilverStripe\\Assets\\Folder'
134
        );
135
    }
136
137
    public function getClientConfig()
138
    {
139
        $baseLink = $this->Link();
140
        return array_merge( parent::getClientConfig(), [
141
            'reactRouter' => true,
142
            'createFileEndpoint' => [
143
                'url' => Controller::join_links($baseLink, 'api/createFile'),
144
                'method' => 'post',
145
                'payloadFormat' => 'urlencoded',
146
            ],
147
            'createFolderEndpoint' => [
148
                'url' => Controller::join_links($baseLink, 'api/createFolder'),
149
                'method' => 'post',
150
                'payloadFormat' => 'urlencoded',
151
            ],
152
            'readFolderEndpoint' => [
153
                'url' => Controller::join_links($baseLink, 'api/readFolder'),
154
                'method' => 'get',
155
                'responseFormat' => 'json',
156
            ],
157
            'searchEndpoint' => [
158
                'url' => Controller::join_links($baseLink, 'api/search'),
159
                'method' => 'get',
160
                'responseFormat' => 'json',
161
            ],
162
            'updateFolderEndpoint' => [
163
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
164
                'method' => 'put',
165
                'payloadFormat' => 'urlencoded',
166
            ],
167
            'deleteEndpoint' => [
168
                'url' => Controller::join_links($baseLink, 'api/delete'),
169
                'method' => 'delete',
170
                'payloadFormat' => 'urlencoded',
171
            ],
172
            'historyEndpoint' => [
173
                'url' => Controller::join_links($baseLink, 'api/history'),
174
                'method' => 'get',
175
                'responseFormat' => 'json'
176
            ],
177
            'limit' => $this->config()->page_length,
178
            'form' => [
179
                'FileEditForm' => [
180
                    'schemaUrl' => $this->Link('schema/FileEditForm')
181
                ],
182
                'FileInsertForm' => [
183
                    'schemaUrl' => $this->Link('schema/FileInsertForm')
184
                ],
185
                'AddToCampaignForm' => [
186
                    'schemaUrl' => $this->Link('schema/AddToCampaignForm')
187
                ],
188
                'FileHistoryForm' => [
189
                    'schemaUrl' => $this->Link('schema/FileHistoryForm')
190
                ]
191
            ],
192
        ]);
193
    }
194
195
    /**
196
     * Fetches a collection of files by ParentID.
197
     *
198
     * @param HTTPRequest $request
199
     * @return HTTPResponse
200
     */
201
    public function apiReadFolder(HTTPRequest $request)
202
    {
203
        $params = $request->requestVars();
204
        $items = array();
205
        $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...
206
        $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...
207
208
        if (!isset($params['id']) && !strlen($params['id'])) {
209
            $this->httpError(400);
210
        }
211
212
        $folderID = (int)$params['id'];
213
        /** @var Folder $folder */
214
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
215
216
        if (!$folder) {
217
            $this->httpError(400);
218
        }
219
220
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
221
        $files = $this->getList()->filter('ParentID', $folderID);
222
223
        if ($files) {
224
            /** @var File $file */
225
            foreach ($files as $file) {
226
                if (!$file->canView()) {
227
                    continue;
228
                }
229
230
                $items[] = $this->getObjectFromData($file);
231
            }
232
        }
233
234
        // Build parents (for breadcrumbs)
235
        $parents = [];
236
        $next = $folder->Parent();
237
        while($next && $next->exists()) {
238
            array_unshift($parents, [
239
                'id' => $next->ID,
240
                'title' => $next->getTitle(),
241
                'filename' => $next->getFilename(),
242
            ]);
243
            if($next->ParentID) {
244
                $next = $next->Parent();
245
            } else {
246
                break;
247
            }
248
        }
249
250
        // Build response
251
        $response = new HTTPResponse();
252
        $response->addHeader('Content-Type', 'application/json');
253
        $response->setBody(json_encode([
254
            'files' => $items,
255
            'title' => $folder->getTitle(),
256
            'count' => count($items),
257
            'parents' => $parents,
258
            'parent' => $parents ? $parents[count($parents) - 1] : null,
259
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
260
            'folderID' => $folderID,
261
            'canEdit' => $folder->canEdit(),
262
            'canDelete' => $folder->canArchive(),
263
        ]));
264
265
        return $response;
266
    }
267
268
    /**
269
     * @param HTTPRequest $request
270
     *
271
     * @return HTTPResponse
272
     */
273
    public function apiSearch(HTTPRequest $request)
274
    {
275
        $params = $request->getVars();
276
        $list = $this->getList($params);
277
278
        $response = new HTTPResponse();
279
        $response->addHeader('Content-Type', 'application/json');
280
        $response->setBody(json_encode([
281
            // Serialisation
282
            "files" => array_map(function($file) {
283
                return $this->getObjectFromData($file);
284
            }, $list->toArray()),
285
            "count" => $list->count(),
286
        ]));
287
288
        return $response;
289
    }
290
291
    /**
292
     * @param HTTPRequest $request
293
     *
294
     * @return HTTPResponse
295
     */
296
    public function apiDelete(HTTPRequest $request)
297
    {
298
        parse_str($request->getBody(), $vars);
299
300
        // CSRF check
301
        $token = SecurityToken::inst();
302 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...
303
            return new HTTPResponse(null, 400);
304
        }
305
306 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...
307
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
308
                ->addHeader('Content-Type', 'application/json');
309
        }
310
311
        $fileIds = $vars['ids'];
312
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
313
314 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...
315
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
316
                ->addHeader('Content-Type', 'application/json');
317
        }
318
319
        if (!min(array_map(function (File $file) {
320
            return $file->canArchive();
321
        }, $files))) {
322
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
323
                ->addHeader('Content-Type', 'application/json');
324
        }
325
326
        /** @var File $file */
327
        foreach ($files as $file) {
328
            $file->doArchive();
329
        }
330
331
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
332
            ->addHeader('Content-Type', 'application/json');
333
    }
334
335
    /**
336
     * Creates a single file based on a form-urlencoded upload.
337
     *
338
     * @param HTTPRequest $request
339
     * @return HTTPRequest|HTTPResponse
340
     */
341
    public function apiCreateFile(HTTPRequest $request)
342
    {
343
        $data = $request->postVars();
344
        $upload = $this->getUpload();
345
346
        // CSRF check
347
        $token = SecurityToken::inst();
348 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...
349
            return new HTTPResponse(null, 400);
350
        }
351
352
        // Check parent record
353
        /** @var Folder $parentRecord */
354
        $parentRecord = null;
355
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
356
            $parentRecord = Folder::get()->byID($data['ParentID']);
357
        }
358
        $data['Parent'] = $parentRecord;
359
360
        $tmpFile = $request->postVar('Upload');
361
        if(!$upload->validate($tmpFile)) {
362
            $result = ['message' => null];
363
            $errors = $upload->getErrors();
364
            if ($message = array_shift($errors)) {
365
                $result['message'] = [
366
                    'type' => 'error',
367
                    'value' => $message,
368
                ];
369
            }
370
            return (new HTTPResponse(json_encode($result), 400))
371
                ->addHeader('Content-Type', 'application/json');
372
        }
373
374
        // TODO Allow batch uploads
375
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
376
        /** @var File $file */
377
        $file = Injector::inst()->create($fileClass);
378
379
        // check canCreate permissions
380 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...
381
            $result = ['message' => [
382
                'type' => 'error',
383
                'value' => _t(
384
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
385
                    'You do not have permission to add files'
386
                )
387
            ]];
388
            return (new HTTPResponse(json_encode($result), 403))
389
                ->addHeader('Content-Type', 'application/json');
390
        }
391
392
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
393 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...
394
            $result = ['message' => [
395
                'type' => 'error',
396
                'value' => _t(
397
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
398
                    'Failed to load file'
399
                )
400
            ]];
401
            return (new HTTPResponse(json_encode($result), 400))
402
                ->addHeader('Content-Type', 'application/json');
403
        }
404
405
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
406
        $file->write();
407
408
        $result = [$this->getObjectFromData($file)];
409
410
        return (new HTTPResponse(json_encode($result)))
411
            ->addHeader('Content-Type', 'application/json');
412
    }
413
414
    /**
415
     * Returns a JSON array for history of a given file ID. Returns a list of all the history.
416
     *
417
     * @param HTTPRequest
418
     *
419
     * @return HTTPResponse
420
     */
421
    public function apiHistory(HTTPRequest $request)
422
    {
423
        // CSRF check not required as the GET request has no side effects.
424
        $fileId = $request->getVar('fileId');
425
426
        if(!$fileId || !is_numeric($fileId)) {
427
            return new HTTPResponse(null, 400);
428
        }
429
430
        $class = File::class;
431
        $file = DataObject::get($class)->byId($fileId);
432
433
        if(!$file) {
434
            return new HTTPResponse(null, 404);
435
        }
436
437
        if(!$file->canView()) {
438
            return new HTTPResponse(null, 403);
439
        }
440
441
        $versions = Versioned::get_all_versions($class, $fileId)
442
            ->limit($this->config()->max_history_entries)
443
            ->sort('Version', 'DESC');
444
445
        $output = array();
446
        $next = array();
447
        $prev = null;
448
449
        // swap the order so we can get the version number to compare against.
450
        // i.e version 3 needs to know version 2 is the previous version
451
        $copy = $versions->map('Version', 'Version')->toArray();
452
        foreach(array_reverse($copy) as $k => $v) {
453
            if($prev) {
454
                $next[$v] = $prev;
455
            }
456
457
            $prev = $v;
458
        }
459
460
        $_cachedMembers = array();
461
462
        foreach($versions as $version) {
463
            $author = null;
464
465
            if($version->AuthorID) {
466
                if(!isset($_cachedMembers[$version->AuthorID])) {
467
                    $_cachedMembers[$version->AuthorID] = DataObject::get(Member::class)
468
                        ->byId($version->AuthorID);
469
                }
470
471
                $author = $_cachedMembers[$version->AuthorID];
472
            }
473
474
            if($version->canView()) {
475
                $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...
476
477
                if(isset($next[$version->Version])) {
478
                    $summary = $version->humanizedChanges(
479
                        $version->Version,
480
                        $next[$version->Version]
481
                    );
482
483
                    // if no summary returned by humanizedChanges, i.e we cannot work out what changed, just show a
484
                    // generic message
485
                    if(!$summary) {
486
                        $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.SAVEDFILE', "Saved file");
487
                    }
488
                } else {
489
                    $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UPLOADEDFILE', "Uploaded file");
490
                }
491
492
                $output[] = array(
493
                    'versionid' => $version->Version,
494
                    'date_ago' => $version->dbObject('LastEdited')->Ago(),
495
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
496
                    'status' => ($version->WasPublished) ? _t('File.PUBLISHED', 'Published') : '',
497
                    'author' => ($author)
498
                        ? $author->Name
499
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UNKNOWN', "Unknown"),
500
                    'summary' => ($summary) ? $summary : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NOSUMMARY', "No summary available")
501
                );
502
            }
503
        }
504
505
        return
506
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
507
508
    }
509
510
511
    /**
512
     * Creates a single folder, within an optional parent folder.
513
     *
514
     * @param HTTPRequest $request
515
     * @return HTTPRequest|HTTPResponse
516
     */
517
    public function apiCreateFolder(HTTPRequest $request)
518
    {
519
        $data = $request->postVars();
520
521
        $class = 'SilverStripe\\Assets\\Folder';
522
523
        // CSRF check
524
        $token = SecurityToken::inst();
525 View Code Duplication
        if (empty($data[$token->getName()]) || !$token->check($data[$token->getName()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
526
            return new HTTPResponse(null, 400);
527
        }
528
529
        // check addchildren permissions
530
        /** @var Folder $parentRecord */
531
        $parentRecord = null;
532
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
533
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
534
        }
535
        $data['Parent'] = $parentRecord;
536
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
537
538
        // Build filename
539
        $baseFilename = isset($data['Name'])
540
            ? basename($data['Name'])
541
            : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NEWFOLDER', "NewFolder");
542
543
        if ($parentRecord && $parentRecord->ID) {
544
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
545
        }
546
547
        // Ensure name is unique
548
        $nameGenerator = $this->getNameGenerator($baseFilename);
549
        $filename = null;
550
        foreach ($nameGenerator as $filename) {
551
            if (! File::find($filename)) {
552
                break;
553
            }
554
        }
555
        $data['Name'] = basename($filename);
556
557
        // Create record
558
        /** @var Folder $record */
559
        $record = Injector::inst()->create($class);
560
561
        // check create permissions
562
        if (!$record->canCreate(null, $data)) {
563
            return (new HTTPResponse(null, 403))
564
                ->addHeader('Content-Type', 'application/json');
565
        }
566
567
        $record->ParentID = $data['ParentID'];
568
        $record->Name = $record->Title = basename($data['Name']);
569
        $record->write();
570
571
        $result = $this->getObjectFromData($record);
572
573
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
574
    }
575
576
    /**
577
     * Redirects 3.x style detail links to new 4.x style routing.
578
     *
579
     * @param HTTPRequest $request
580
     */
581
    public function legacyRedirectForEditView($request)
582
    {
583
        $fileID = $request->param('FileID');
584
        /** @var File $file */
585
        $file = File::get()->byID($fileID);
586
        $link = $this->getFileEditLink($file) ?: $this->Link();
587
        $this->redirect($link);
588
    }
589
590
    /**
591
     * Given a file return the CMS link to edit it
592
     *
593
     * @param File $file
594
     * @return string
595
     */
596
    public function getFileEditLink($file)
597
    {
598
        if(!$file || !$file->isInDB()) {
599
            return null;
600
        }
601
602
        return Controller::join_links(
603
            $this->Link('show'),
604
            $file->ParentID,
605
            'edit',
606
            $file->ID
607
        );
608
    }
609
610
    /**
611
     * Get the search context from {@link File}, used to create the search form
612
     * as well as power the /search API endpoint.
613
     *
614
     * @return SearchContext
615
     */
616
    public function getSearchContext()
617
    {
618
        $context = File::singleton()->getDefaultSearchContext();
619
620
        // Customize fields
621
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
622
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
623
        ->setConfig('showcalendar', true);
624
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
625
        ->setConfig('showcalendar', true);
626
        $dateGroup = FieldGroup::create(
627
            $dateHeader,
628
            $dateFrom,
629
            $dateTo
630
        );
631
        $context->addField($dateGroup);
632
        /** @skipUpgrade */
633
        $appCategories = array(
634
            'archive' => _t(
635
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryArchive',
636
                'Archive'
637
            ),
638
            'audio' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryAudio', 'Audio'),
639
            'document' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryDocument', 'Document'),
640
            'flash' => _t(
641
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryFlash',
642
                'Flash',
643
                'The fileformat'
644
            ),
645
            'image' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryImage', 'Image'),
646
            'video' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryVideo', 'Video'),
647
        );
648
        $context->addField(
649
            $typeDropdown = new DropdownField(
650
                'AppCategory',
651
                _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.Filetype', 'File type'),
652
                $appCategories
653
            )
654
        );
655
656
        $typeDropdown->setEmptyString(' ');
657
658
        $currentfolderLabel = _t(
659
            'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CurrentFolderOnly',
660
            'Limit to current folder?'
661
        );
662
        $context->addField(
663
            new CheckboxField('CurrentFolderOnly', $currentfolderLabel)
664
        );
665
        $context->getFields()->removeByName('Title');
666
667
        return $context;
668
    }
669
670
    /**
671
     * Get an asset renamer for the given filename.
672
     *
673
     * @param  string             $filename Path name
674
     * @return AssetNameGenerator
675
     */
676
    protected function getNameGenerator($filename)
677
    {
678
        return Injector::inst()
679
            ->createWithArgs('AssetNameGenerator', array($filename));
680
    }
681
682
    /**
683
     * @todo Implement on client
684
     *
685
     * @param bool $unlinked
686
     * @return ArrayList
687
     */
688
    public function breadcrumbs($unlinked = false)
689
    {
690
        return null;
691
    }
692
693
694
    /**
695
     * Don't include class namespace in auto-generated CSS class
696
     */
697
    public function baseCSSClasses()
698
    {
699
        return 'AssetAdmin LeftAndMain';
700
    }
701
702
    public function providePermissions()
703
    {
704
        return array(
705
            "CMS_ACCESS_AssetAdmin" => array(
706
                'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array(
0 ignored issues
show
Documentation introduced by
array('title' => static::menu_title()) is of type array<string,string,{"title":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
707
                    'title' => static::menu_title()
708
                )),
709
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
710
            )
711
        );
712
    }
713
714
    /**
715
     * Build a form scaffolder for this model
716
     *
717
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
718
     *
719
     * @param File $file
720
     * @return FormFactory
721
     */
722
    public function getFormFactory(File $file)
723
    {
724
        // Get service name based on file class
725
        $name = null;
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
726
        if ($file instanceof Folder) {
727
            $name = FolderFormFactory::class;
728
        } elseif ($file instanceof Image) {
729
            $name = ImageFormFactory::class;
730
        } else {
731
            $name = FileFormFactory::class;
732
        }
733
        return Injector::inst()->get($name);
734
    }
735
736
    /**
737
     * The form is used to generate a form schema,
738
     * as well as an intermediary object to process data through API endpoints.
739
     * Since it's used directly on API endpoints, it does not have any form actions.
740
     * It handles both {@link File} and {@link Folder} records.
741
     *
742
     * @param int $id
743
     * @return Form
744
     */
745
    public function getFileEditForm($id)
746
    {
747
        /** @var File $file */
748
        $file = $this->getList()->byID($id);
749
750 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...
751
            $this->httpError(403, _t(
752
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
753
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
754
                '',
755
                ['ObjectTitle' => $file->i18n_singular_name()]
756
            ));
757
            return null;
758
        }
759
760
        $scaffolder = $this->getFormFactory($file);
761
        $form = $scaffolder->getForm($this, 'FileEditForm', [
762
            'Record' => $file
763
        ]);
764
765
        // Configure form to respond to validation errors with form schema
766
        // if requested via react.
767 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...
768
            $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $file->exists() ? $file->ID : '');
769
            return $this->getSchemaResponse($form, $schemaId);
770
        });
771
772
        return $form;
773
    }
774
775
    /**
776
     * Get file edit form
777
     *
778
     * @return Form
779
     */
780 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...
781
    {
782
        // Get ID either from posted back value, or url parameter
783
        $request = $this->getRequest();
784
        $id = $request->param('ID') ?: $request->postVar('ID');
785
        return $this->getFileEditForm($id);
786
    }
787
    
788
    /**
789
     * The form is used to generate a form schema,
790
     * as well as an intermediary object to process data through API endpoints.
791
     * Since it's used directly on API endpoints, it does not have any form actions.
792
     * It handles both {@link File} and {@link Folder} records.
793
     *
794
     * @param int $id
795
     * @return Form
796
     */
797
    public function getFileInsertForm($id)
798
    {
799
        /** @var File $file */
800
        $file = $this->getList()->byID($id);
801
        
802 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...
803
            $this->httpError(403, _t(
804
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
805
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
806
                '',
807
                ['ObjectTitle' => $file->i18n_singular_name()]
808
            ));
809
            return null;
810
        }
811
        
812
        $scaffolder = $this->getFormFactory($file);
813
        $form = $scaffolder->getForm($this, 'FileInsertForm', [
814
            'Record' => $file,
815
            'Type' => 'insert',
816
        ]);
817
        
818
        return $form;
819
    }
820
    
821
    /**
822
     * Get file insert form
823
     *
824
     * @return Form
825
     */
826 View Code Duplication
    public function FileInsertForm()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
827
    {
828
        // Get ID either from posted back value, or url parameter
829
        $request = $this->getRequest();
830
        $id = $request->param('ID') ?: $request->postVar('ID');
831
        return $this->getFileInsertForm($id);
832
    }
833
834
    /**
835
     *
836
     */
837
    public function getFileHistoryForm($id)
838
    {
839
        /** @var File $file */
840
        $file = $this->getList()->byID($id);
841
842
        $request = $this->getRequest();
843
844 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...
845
            $this->httpError(403, _t(
846
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
847
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
848
                '',
849
                ['ObjectTitle' => $file->i18n_singular_name()]
850
            ));
851
            return null;
852
        }
853
854
        $versionId = $request->param('OtherItemID');
855
        $version = Versioned::get_version($this->getList()->dataClass(), $id, $versionId);
856
857
        if(!$version) {
858
            return $this->httpError(404);
859
        }
860
861
        if(!$version->canView()) {
862
            return $this->httpError(403);
863
        }
864
865
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
866
        $form = $scaffolder->getForm($this, 'FileHistoryForm', [
867
            'Record' => $version
868
        ]);
869
870
        // Configure form to respond to validation errors with form schema
871
        // if requested via react.
872 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...
873
            $schemaId = Controller::join_links($this->Link('schema/FileHistoryForm'), $file->exists() ? $file->ID : '');
874
875
            return $this->getSchemaResponse($form, $schemaId);
876
        });
877
878
        $form->makeReadonly();
879
880
        return $form;
881
    }
882
883
    /**
884
     * Get file history form
885
     *
886
     * @return Form
887
     */
888 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...
889
    {
890
        $request = $this->getRequest();
891
        $id = $request->param('ID') ?: $request->postVar('ID');
892
        $form = $this->getFileHistoryForm($id);
893
894
        return $form;
895
    }
896
897
    /**
898
     * @param array $data
899
     * @param Form $form
900
     * @return HTTPResponse
901
     */
902
    public function save($data, $form)
903
    {
904
        return $this->saveOrPublish($data, $form, false);
905
    }
906
907
    /**
908
     * @param array $data
909
     * @param Form $form
910
     * @return HTTPResponse
911
     */
912
    public function publish($data, $form)
913
    {
914
        return $this->saveOrPublish($data, $form, true);
915
    }
916
917
    /**
918
     * Update thisrecord
919
     *
920
     * @param array $data
921
     * @param Form $form
922
     * @param bool $doPublish
923
     * @return HTTPResponse
924
     */
925
    protected function saveOrPublish($data, $form, $doPublish = false)
926
    {
927 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...
928
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
929
                ->addHeader('Content-Type', 'application/json');
930
        }
931
932
        $id = (int) $data['ID'];
933
        /** @var File $record */
934
        $record = $this->getList()->filter('ID', $id)->first();
935
936 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...
937
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
938
                ->addHeader('Content-Type', 'application/json');
939
        }
940
941
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
942
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
943
                ->addHeader('Content-Type', 'application/json');
944
        }
945
946
        $form->saveInto($record);
947
        $record->write();
948
949
        // Publish this record and owned objects
950
        if ($doPublish) {
951
            $record->publishRecursive();
952
        }
953
954
        // Return the record data in the same response as the schema to save a postback
955
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
956
        $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...
957
        $schemaData['record'] = $this->getObjectFromData($record);
958
        $response = new HTTPResponse(Convert::raw2json($schemaData));
959
        $response->addHeader('Content-Type', 'application/json');
960
        return $response;
961
    }
962
963
    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...
964
    {
965 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...
966
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
967
                ->addHeader('Content-Type', 'application/json');
968
        }
969
970
        $id = (int) $data['ID'];
971
        /** @var File $record */
972
        $record = $this->getList()->filter('ID', $id)->first();
973
974
        if (!$record) {
975
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
976
                ->addHeader('Content-Type', 'application/json');
977
        }
978
979
        if (!$record->canUnpublish()) {
980
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
981
                ->addHeader('Content-Type', 'application/json');
982
        }
983
984
        $record->doUnpublish();
985
986
        // Return the record data in the same response as the schema to save a postback
987
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
988
        $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...
989
        $schemaData['record'] = $this->getObjectFromData($record);
990
        $response = new HTTPResponse(Convert::raw2json($schemaData));
991
        $response->addHeader('Content-Type', 'application/json');
992
        return $response;
993
    }
994
995
    /**
996
     * @param File $file
997
     *
998
     * @return array
999
     */
1000
    protected function getObjectFromData(File $file)
1001
    {
1002
        $object = array(
1003
            'id' => $file->ID,
1004
            'created' => $file->Created,
1005
            'lastUpdated' => $file->LastEdited,
1006
            'owner' => null,
1007
            'parent' => null,
1008
            'title' => $file->Title,
1009
            'exists' => $file->exists(), // Broken file check
1010
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
1011
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
1012
            'name' => $file->Name,
1013
            'filename' => $file->Filename,
1014
            '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...
1015
            'size' => $file->Size,
1016
            'url' => $file->AbsoluteURL,
1017
            'published' => $file->isPublished(),
1018
            'modified' => $file->isModifiedOnDraft(),
1019
            'draft' => $file->isOnDraftOnly(),
1020
            'canEdit' => $file->canEdit(),
1021
            'canDelete' => $file->canArchive(),
1022
        );
1023
1024
        /** @var Member $owner */
1025
        $owner = $file->Owner();
1026
1027
        if ($owner) {
1028
            $object['owner'] = array(
1029
                'id' => $owner->ID,
1030
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1031
            );
1032
        }
1033
1034
        /** @var Folder $parent */
1035
        $parent = $file->Parent();
1036
1037
        if ($parent) {
1038
            $object['parent'] = array(
1039
                'id' => $parent->ID,
1040
                'title' => $parent->Title,
1041
                'filename' => $parent->Filename,
1042
            );
1043
        }
1044
1045
        /** @var File $file */
1046
        if ($file->getIsImage()) {
1047
            $width = (int)Config::inst()->get(self::class, 'thumbnail_width');
1048
            $height = (int)Config::inst()->get(self::class, 'thumbnail_height');
1049
1050
            $thumbnail = $file->FitMax($width, $height);
1051
            if ($thumbnail && $thumbnail->exists()) {
1052
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1053
            }
1054
            $object['dimensions']['width'] = $file->Width;
1055
            $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...
1056
        }
1057
1058
        return $object;
1059
    }
1060
1061
    /**
1062
     * Returns the files and subfolders contained in the currently selected folder,
1063
     * defaulting to the root node. Doubles as search results, if any search parameters
1064
     * are set through {@link SearchForm()}.
1065
     *
1066
     * @param array $params Unsanitised request parameters
1067
     * @return DataList
1068
     */
1069
    protected function getList($params = array())
1070
    {
1071
        $context = $this->getSearchContext();
1072
1073
        // Overwrite name filter to search both Name and Title attributes
1074
        $context->removeFilterByName('Name');
1075
1076
        // Lazy loaded list. Allows adding new filters through SearchContext.
1077
        /** @var DataList $list */
1078
        $list = $context->getResults($params);
1079
1080
        // Re-add previously removed "Name" filter as combined filter
1081
        // TODO Replace with composite SearchFilter once that API exists
1082
        if(!empty($params['Name'])) {
1083
            $list = $list->filterAny(array(
1084
                'Name:PartialMatch' => $params['Name'],
1085
                'Title:PartialMatch' => $params['Name']
1086
            ));
1087
        }
1088
1089
        // Optionally limit search to a folder (non-recursive)
1090
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1091
            $list = $list->filter('ParentID', $params['ParentID']);
1092
        }
1093
1094
        // Date filtering
1095 View Code Duplication
        if (!empty($params['CreatedFrom'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1096
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1097
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1098
        }
1099 View Code Duplication
        if (!empty($params['CreatedTo'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1100
            $toDate = new DateField(null, null, $params['CreatedTo']);
1101
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1102
        }
1103
1104
        // Categories
1105
        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...
1106
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1107
            $list = $list->filter('Name:PartialMatch', $extensions);
1108
        }
1109
1110
        // Sort folders first
1111
        $list = $list->sort(
1112
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1113
        );
1114
1115
        // Pagination
1116
        if (isset($filters['page']) && isset($filters['limit'])) {
1117
            $page = $filters['page'];
1118
            $limit = $filters['limit'];
1119
            $offset = ($page - 1) * $limit;
1120
            $list = $list->limit($limit, $offset);
1121
        }
1122
1123
        // Access checks
1124
        $list = $list->filterByCallback(function(File $file) {
1125
            return $file->canView();
1126
        });
1127
1128
        return $list;
1129
    }
1130
1131
    /**
1132
     * Action handler for adding pages to a campaign
1133
     *
1134
     * @param array $data
1135
     * @param Form $form
1136
     * @return DBHTMLText|HTTPResponse
1137
     */
1138
    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...
1139
    {
1140
        $id = $data['ID'];
1141
        $record = $this->getList()->byID($id);
1142
1143
        $handler = AddToCampaignHandler::create($this, $record);
1144
        $results = $handler->addToCampaign($record, $data['Campaign']);
1145
        if (!is_null($results)) {
1146
            $request = $this->getRequest();
1147
            if($request->getHeader('X-Formschema-Request')) {
1148
                $data = $this->getSchemaForForm($handler->Form($record));
1149
                $data['message'] = $results;
1150
1151
                $response = new HTTPResponse(Convert::raw2json($data));
1152
                $response->addHeader('Content-Type', 'application/json');
1153
                return $response;
1154
            }
1155
            return $results;
1156
        }
1157
    }
1158
1159
    /**
1160
     * Url handler for add to campaign form
1161
     *
1162
     * @param HTTPRequest $request
1163
     * @return Form
1164
     */
1165
    public function AddToCampaignForm($request)
1166
    {
1167
        // Get ID either from posted back value, or url parameter
1168
        $id = $request->param('ID') ?: $request->postVar('ID');
1169
        return $this->getAddToCampaignForm($id);
1170
    }
1171
1172
    /**
1173
     * @param int $id
1174
     * @return Form
1175
     */
1176
    public function getAddToCampaignForm($id)
1177
    {
1178
        // Get record-specific fields
1179
        $record = $this->getList()->byID($id);
1180
1181
        if (!$record) {
1182
            $this->httpError(404, _t(
1183
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1184
                'That {Type} couldn\'t be found',
1185
                '',
1186
                ['Type' => File::singleton()->i18n_singular_name()]
1187
            ));
1188
            return null;
1189
        }
1190 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...
1191
            $this->httpError(403, _t(
1192
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1193
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1194
                '',
1195
                ['ObjectTitle' => $record->i18n_singular_name()]
1196
            ));
1197
            return null;
1198
        }
1199
1200
        $handler = AddToCampaignHandler::create($this, $record);
1201
        $form = $handler->Form($record);
1202
1203 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...
1204
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1205
            return $this->getSchemaResponse($form, $schemaId);
1206
        });
1207
1208
        return $form;
1209
    }
1210
1211
    /**
1212
     * @return Upload
1213
     */
1214
    protected function getUpload()
1215
    {
1216
        $upload = Upload::create();
1217
        $upload->getValidator()->setAllowedExtensions(
1218
            // filter out '' since this would be a regex problem on JS end
1219
            array_filter(File::config()->get('allowed_extensions'))
1220
        );
1221
1222
        return $upload;
1223
    }
1224
}
1225