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

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1012
            'size' => $file->Size,
1013
            'url' => $file->AbsoluteURL,
1014
            'published' => $file->isPublished(),
1015
            'modified' => $file->isModifiedOnDraft(),
1016
            'draft' => $file->isOnDraftOnly(),
1017
            'canEdit' => $file->canEdit(),
1018
            'canDelete' => $file->canArchive(),
1019
        );
1020
1021
        /** @var Member $owner */
1022
        $owner = $file->Owner();
1023
1024
        if ($owner) {
1025
            $object['owner'] = array(
1026
                'id' => $owner->ID,
1027
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1028
            );
1029
        }
1030
1031
        /** @var Folder $parent */
1032
        $parent = $file->Parent();
1033
1034
        if ($parent) {
1035
            $object['parent'] = array(
1036
                'id' => $parent->ID,
1037
                'title' => $parent->Title,
1038
                'filename' => $parent->Filename,
1039
            );
1040
        }
1041
1042
        /** @var File $file */
1043
        if ($file->getIsImage()) {
1044
            // Small thumbnail
1045
            $smallWidth = UploadField::config()->get('thumbnail_width');
1046
            $smallHeight = UploadField::config()->get('thumbnail_height');
1047
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1048
            if ($smallThumbnail && $smallThumbnail->exists()) {
1049
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1050
            }
1051
1052
            // Large thumbnail
1053
            $width = $this->config()->get('thumbnail_width');
1054
            $height = $this->config()->get('thumbnail_height');
1055
            $thumbnail = $file->FitMax($width, $height);
1056
            if ($thumbnail && $thumbnail->exists()) {
1057
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1058
            }
1059
            $object['dimensions']['width'] = $file->Width;
1060
            $object['dimensions']['height'] = $file->Height;
0 ignored issues
show
Bug introduced by
The property Height does not seem to exist. Did you mean asset_preview_height?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1061
        }
1062
1063
        return $object;
1064
    }
1065
1066
    /**
1067
     * Returns the files and subfolders contained in the currently selected folder,
1068
     * defaulting to the root node. Doubles as search results, if any search parameters
1069
     * are set through {@link SearchForm()}.
1070
     *
1071
     * @param array $params Unsanitised request parameters
1072
     * @return DataList
1073
     */
1074
    protected function getList($params = array())
1075
    {
1076
        $context = $this->getSearchContext();
1077
1078
        // Overwrite name filter to search both Name and Title attributes
1079
        $context->removeFilterByName('Name');
1080
1081
        // Lazy loaded list. Allows adding new filters through SearchContext.
1082
        /** @var DataList $list */
1083
        $list = $context->getResults($params);
1084
1085
        // Re-add previously removed "Name" filter as combined filter
1086
        // TODO Replace with composite SearchFilter once that API exists
1087
        if(!empty($params['Name'])) {
1088
            $list = $list->filterAny(array(
1089
                'Name:PartialMatch' => $params['Name'],
1090
                'Title:PartialMatch' => $params['Name']
1091
            ));
1092
        }
1093
1094
        // Optionally limit search to a folder (non-recursive)
1095
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1096
            $list = $list->filter('ParentID', $params['ParentID']);
1097
        }
1098
1099
        // Date filtering
1100 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...
1101
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1102
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1103
        }
1104 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...
1105
            $toDate = new DateField(null, null, $params['CreatedTo']);
1106
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1107
        }
1108
1109
        // Categories
1110
        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...
1111
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1112
            $list = $list->filter('Name:PartialMatch', $extensions);
1113
        }
1114
1115
        // Sort folders first
1116
        $list = $list->sort(
1117
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1118
        );
1119
1120
        // Pagination
1121
        if (isset($filters['page']) && isset($filters['limit'])) {
1122
            $page = $filters['page'];
1123
            $limit = $filters['limit'];
1124
            $offset = ($page - 1) * $limit;
1125
            $list = $list->limit($limit, $offset);
1126
        }
1127
1128
        // Access checks
1129
        $list = $list->filterByCallback(function(File $file) {
1130
            return $file->canView();
1131
        });
1132
1133
        return $list;
1134
    }
1135
1136
    /**
1137
     * Action handler for adding pages to a campaign
1138
     *
1139
     * @param array $data
1140
     * @param Form $form
1141
     * @return DBHTMLText|HTTPResponse
1142
     */
1143
    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...
1144
    {
1145
        $id = $data['ID'];
1146
        $record = $this->getList()->byID($id);
1147
1148
        $handler = AddToCampaignHandler::create($this, $record);
1149
        $results = $handler->addToCampaign($record, $data['Campaign']);
1150
        if (!isset($results)) {
1151
            return null;
1152
        }
1153
        $request = $this->getRequest();
1154
        if($request->getHeader('X-Formschema-Request')) {
1155
            $data = $this->getSchemaForForm($handler->Form($record));
1156
            $data['message'] = $results;
1157
1158
            $response = new HTTPResponse(Convert::raw2json($data));
1159
            $response->addHeader('Content-Type', 'application/json');
1160
            return $response;
1161
        }
1162
        return $results;
1163
    }
1164
1165
    /**
1166
     * Url handler for add to campaign form
1167
     *
1168
     * @param HTTPRequest $request
1169
     * @return Form
1170
     */
1171
    public function AddToCampaignForm($request)
1172
    {
1173
        // Get ID either from posted back value, or url parameter
1174
        $id = $request->param('ID') ?: $request->postVar('ID');
1175
        return $this->getAddToCampaignForm($id);
1176
    }
1177
1178
    /**
1179
     * @param int $id
1180
     * @return Form
1181
     */
1182
    public function getAddToCampaignForm($id)
1183
    {
1184
        // Get record-specific fields
1185
        $record = $this->getList()->byID($id);
1186
1187
        if (!$record) {
1188
            $this->httpError(404, _t(
1189
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1190
                'That {Type} couldn\'t be found',
1191
                '',
1192
                ['Type' => File::singleton()->i18n_singular_name()]
1193
            ));
1194
            return null;
1195
        }
1196 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...
1197
            $this->httpError(403, _t(
1198
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1199
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1200
                '',
1201
                ['ObjectTitle' => $record->i18n_singular_name()]
1202
            ));
1203
            return null;
1204
        }
1205
1206
        $handler = AddToCampaignHandler::create($this, $record);
1207
        $form = $handler->Form($record);
1208
1209 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...
1210
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1211
            return $this->getSchemaResponse($form, $schemaId);
1212
        });
1213
1214
        return $form;
1215
    }
1216
1217
    /**
1218
     * @return Upload
1219
     */
1220
    protected function getUpload()
1221
    {
1222
        $upload = Upload::create();
1223
        $upload->getValidator()->setAllowedExtensions(
1224
            // filter out '' since this would be a regex problem on JS end
1225
            array_filter(File::config()->get('allowed_extensions'))
1226
        );
1227
1228
        return $upload;
1229
    }
1230
}
1231