Completed
Pull Request — master (#289)
by Ingo
02:07
created

AssetAdmin::getfileEditForm()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 29
Code Lines 16

Duplication

Lines 13
Ratio 44.83 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 13
loc 29
rs 8.8571
cc 3
eloc 16
nc 2
nop 1
1
<?php
2
3
namespace SilverStripe\AssetAdmin\Controller;
4
5
use InvalidArgumentException;
6
use SilverStripe\Admin\AddToCampaignHandler;
7
use SilverStripe\Admin\CMSBatchActionHandler;
8
use SilverStripe\Admin\LeftAndMain;
9
use SilverStripe\AssetAdmin\Forms\UploadField;
10
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
11
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
12
use SilverStripe\AssetAdmin\Forms\FileHistoryFormFactory;
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\Convert;
23
use SilverStripe\Core\Injector\Injector;
24
use SilverStripe\Forms\CheckboxField;
25
use SilverStripe\Forms\DateField;
26
use SilverStripe\Forms\DropdownField;
27
use SilverStripe\Forms\FieldGroup;
28
use SilverStripe\Forms\Form;
29
use SilverStripe\Forms\FormFactory;
30
use SilverStripe\Forms\HeaderField;
31
use SilverStripe\ORM\ArrayList;
32
use SilverStripe\ORM\DataList;
33
use SilverStripe\ORM\DataObject;
34
use SilverStripe\ORM\FieldType\DBHTMLText;
35
use SilverStripe\ORM\Search\SearchContext;
36
use SilverStripe\Security\Member;
37
use SilverStripe\Security\PermissionProvider;
38
use SilverStripe\Security\SecurityToken;
39
use SilverStripe\View\Requirements;
40
use SilverStripe\ORM\Versioning\Versioned;
41
42
/**
43
 * AssetAdmin is the 'file store' section of the CMS.
44
 * It provides an interface for manipulating the File and Folder objects in the system.
45
 */
46
class AssetAdmin extends LeftAndMain implements PermissionProvider
47
{
48
    private static $url_segment = 'assets';
0 ignored issues
show
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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
49
50
    private static $url_rule = '/$Action/$ID';
0 ignored issues
show
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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
51
52
    private static $menu_title = 'Files';
0 ignored issues
show
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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
53
54
    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...
55
56
    private static $url_handlers = [
0 ignored issues
show
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...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
57
        // Legacy redirect for SS3-style detail view
58
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
59
        // Pass all URLs to the index, for React to unpack
60
        'show/$FolderID/edit/$FileID' => 'index',
61
        // API access points with structured data
62
        'POST api/createFolder' => 'apiCreateFolder',
63
        'POST api/createFile' => 'apiCreateFile',
64
        'GET api/readFolder' => 'apiReadFolder',
65
        'PUT api/updateFolder' => 'apiUpdateFolder',
66
        'DELETE api/delete' => 'apiDelete',
67
        'GET api/search' => 'apiSearch',
68
        'GET api/history' => 'apiHistory'
69
    ];
70
71
    /**
72
     * Amount of results showing on a single page.
73
     *
74
     * @config
75
     * @var int
76
     */
77
    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...
78
79
    /**
80
     * @config
81
     * @see Upload->allowedMaxFileSize
82
     * @var int
83
     */
84
    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...
85
86
    /**
87
     * @config
88
     *
89
     * @var int
90
     */
91
    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...
92
93
    /**
94
     * @var array
95
     */
96
    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...
97
        'legacyRedirectForEditView',
98
        'apiCreateFolder',
99
        'apiCreateFile',
100
        'apiReadFolder',
101
        'apiUpdateFolder',
102
        'apiHistory',
103
        'apiDelete',
104
        'apiSearch',
105
        'fileEditForm',
106
        'fileHistoryForm',
107
        'addToCampaignForm',
108
        'fileInsertForm',
109
        'schema',
110
    );
111
112
    private static $required_permission_codes = 'CMS_ACCESS_AssetAdmin';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $required_permission_codes is not used and could be removed.

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

Loading history...
113
114
    private static $thumbnail_width = 400;
0 ignored issues
show
Unused Code introduced by
The property $thumbnail_width is not used and could be removed.

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

Loading history...
115
116
    private static $thumbnail_height = 300;
0 ignored issues
show
Unused Code introduced by
The property $thumbnail_height is not used and could be removed.

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

Loading history...
117
118
    /**
119
     * Set up the controller
120
     */
121
    public function init()
122
    {
123
        parent::init();
124
125
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
126
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
127
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
128
129
        CMSBatchActionHandler::register(
130
            'delete',
131
            'SilverStripe\AssetAdmin\BatchAction\DeleteAssets',
132
            'SilverStripe\\Assets\\Folder'
133
        );
134
    }
135
136
    public function getClientConfig()
137
    {
138
        $baseLink = $this->Link();
139
        return array_merge(parent::getClientConfig(), [
140
            'reactRouter' => true,
141
            'createFileEndpoint' => [
142
                'url' => Controller::join_links($baseLink, 'api/createFile'),
143
                'method' => 'post',
144
                'payloadFormat' => 'urlencoded',
145
            ],
146
            'createFolderEndpoint' => [
147
                'url' => Controller::join_links($baseLink, 'api/createFolder'),
148
                'method' => 'post',
149
                'payloadFormat' => 'urlencoded',
150
            ],
151
            'readFolderEndpoint' => [
152
                'url' => Controller::join_links($baseLink, 'api/readFolder'),
153
                'method' => 'get',
154
                'responseFormat' => 'json',
155
            ],
156
            'searchEndpoint' => [
157
                'url' => Controller::join_links($baseLink, 'api/search'),
158
                'method' => 'get',
159
                'responseFormat' => 'json',
160
            ],
161
            'updateFolderEndpoint' => [
162
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
163
                'method' => 'put',
164
                'payloadFormat' => 'urlencoded',
165
            ],
166
            'deleteEndpoint' => [
167
                'url' => Controller::join_links($baseLink, 'api/delete'),
168
                'method' => 'delete',
169
                'payloadFormat' => 'urlencoded',
170
            ],
171
            'historyEndpoint' => [
172
                'url' => Controller::join_links($baseLink, 'api/history'),
173
                'method' => 'get',
174
                'responseFormat' => 'json'
175
            ],
176
            'limit' => $this->config()->page_length,
177
            'form' => [
178
                'fileEditForm' => [
179
                    'schemaUrl' => $this->Link('schema/fileEditForm')
180
                ],
181
                'fileInsertForm' => [
182
                    'schemaUrl' => $this->Link('schema/FileInsertForm')
183
                ],
184
                'addToCampaignForm' => [
185
                    'schemaUrl' => $this->Link('schema/addToCampaignForm')
186
                ],
187
                'fileHistoryForm' => [
188
                    'schemaUrl' => $this->Link('schema/fileHistoryForm')
189
                ]
190
            ],
191
        ]);
192
    }
193
194
    /**
195
     * Fetches a collection of files by ParentID.
196
     *
197
     * @param HTTPRequest $request
198
     * @return HTTPResponse
199
     */
200
    public function apiReadFolder(HTTPRequest $request)
201
    {
202
        $params = $request->requestVars();
203
        $items = array();
204
        $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...
205
        $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...
206
207
        if (!isset($params['id']) && !strlen($params['id'])) {
208
            $this->httpError(400);
209
        }
210
211
        $folderID = (int)$params['id'];
212
        /** @var Folder $folder */
213
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
214
215
        if (!$folder) {
216
            $this->httpError(400);
217
        }
218
219
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
220
        $files = $this->getList()->filter('ParentID', $folderID);
221
222
        if ($files) {
223
            /** @var File $file */
224
            foreach ($files as $file) {
225
                if (!$file->canView()) {
226
                    continue;
227
                }
228
229
                $items[] = $this->getObjectFromData($file);
230
            }
231
        }
232
233
        // Build parents (for breadcrumbs)
234
        $parents = [];
235
        $next = $folder->Parent();
236
        while ($next && $next->exists()) {
237
            array_unshift($parents, [
238
                'id' => $next->ID,
239
                'title' => $next->getTitle(),
240
                'filename' => $next->getFilename(),
241
            ]);
242
            if ($next->ParentID) {
243
                $next = $next->Parent();
244
            } else {
245
                break;
246
            }
247
        }
248
249
        // Build response
250
        $response = new HTTPResponse();
251
        $response->addHeader('Content-Type', 'application/json');
252
        $response->setBody(json_encode([
253
            'files' => $items,
254
            'title' => $folder->getTitle(),
255
            'count' => count($items),
256
            'parents' => $parents,
257
            'parent' => $parents ? $parents[count($parents) - 1] : null,
258
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
259
            'folderID' => $folderID,
260
            'canEdit' => $folder->canEdit(),
261
            'canDelete' => $folder->canArchive(),
262
        ]));
263
264
        return $response;
265
    }
266
267
    /**
268
     * @param HTTPRequest $request
269
     *
270
     * @return HTTPResponse
271
     */
272
    public function apiSearch(HTTPRequest $request)
273
    {
274
        $params = $request->getVars();
275
        $list = $this->getList($params);
276
277
        $response = new HTTPResponse();
278
        $response->addHeader('Content-Type', 'application/json');
279
        $response->setBody(json_encode([
280
            // Serialisation
281
            "files" => array_map(function ($file) {
282
                return $this->getObjectFromData($file);
283
            }, $list->toArray()),
284
            "count" => $list->count(),
285
        ]));
286
287
        return $response;
288
    }
289
290
    /**
291
     * @param HTTPRequest $request
292
     *
293
     * @return HTTPResponse
294
     */
295
    public function apiDelete(HTTPRequest $request)
296
    {
297
        parse_str($request->getBody(), $vars);
298
299
        // CSRF check
300
        $token = SecurityToken::inst();
301 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...
302
            return new HTTPResponse(null, 400);
303
        }
304
305 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...
306
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
307
                ->addHeader('Content-Type', 'application/json');
308
        }
309
310
        $fileIds = $vars['ids'];
311
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
312
313 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...
314
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
315
                ->addHeader('Content-Type', 'application/json');
316
        }
317
318
        if (!min(array_map(function (File $file) {
319
            return $file->canArchive();
320
        }, $files))) {
321
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
322
                ->addHeader('Content-Type', 'application/json');
323
        }
324
325
        /** @var File $file */
326
        foreach ($files as $file) {
327
            $file->doArchive();
328
        }
329
330
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
331
            ->addHeader('Content-Type', 'application/json');
332
    }
333
334
    /**
335
     * Creates a single file based on a form-urlencoded upload.
336
     *
337
     * @param HTTPRequest $request
338
     * @return HTTPRequest|HTTPResponse
339
     */
340
    public function apiCreateFile(HTTPRequest $request)
341
    {
342
        $data = $request->postVars();
343
        $upload = $this->getUpload();
344
345
        // CSRF check
346
        $token = SecurityToken::inst();
347 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...
348
            return new HTTPResponse(null, 400);
349
        }
350
351
        // Check parent record
352
        /** @var Folder $parentRecord */
353
        $parentRecord = null;
354
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
355
            $parentRecord = Folder::get()->byID($data['ParentID']);
356
        }
357
        $data['Parent'] = $parentRecord;
358
359
        $tmpFile = $request->postVar('Upload');
360
        if (!$upload->validate($tmpFile)) {
361
            $result = ['message' => null];
362
            $errors = $upload->getErrors();
363
            if ($message = array_shift($errors)) {
364
                $result['message'] = [
365
                    'type' => 'error',
366
                    'value' => $message,
367
                ];
368
            }
369
            return (new HTTPResponse(json_encode($result), 400))
370
                ->addHeader('Content-Type', 'application/json');
371
        }
372
373
        // TODO Allow batch uploads
374
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
375
        /** @var File $file */
376
        $file = Injector::inst()->create($fileClass);
377
378
        // check canCreate permissions
379 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...
380
            $result = ['message' => [
381
                'type' => 'error',
382
                'value' => _t(
383
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
384
                    'You do not have permission to add files'
385
                )
386
            ]];
387
            return (new HTTPResponse(json_encode($result), 403))
388
                ->addHeader('Content-Type', 'application/json');
389
        }
390
391
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
392 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...
393
            $result = ['message' => [
394
                'type' => 'error',
395
                'value' => _t(
396
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
397
                    'Failed to load file'
398
                )
399
            ]];
400
            return (new HTTPResponse(json_encode($result), 400))
401
                ->addHeader('Content-Type', 'application/json');
402
        }
403
404
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
405
        $file->write();
406
407
        $result = [$this->getObjectFromData($file)];
408
409
        return (new HTTPResponse(json_encode($result)))
410
            ->addHeader('Content-Type', 'application/json');
411
    }
412
413
    /**
414
     * Returns a JSON array for history of a given file ID. Returns a list of all the history.
415
     *
416
     * @param HTTPRequest
417
     *
418
     * @return HTTPResponse
419
     */
420
    public function apiHistory(HTTPRequest $request)
421
    {
422
        // CSRF check not required as the GET request has no side effects.
423
        $fileId = $request->getVar('fileId');
424
425
        if (!$fileId || !is_numeric($fileId)) {
426
            return new HTTPResponse(null, 400);
427
        }
428
429
        $class = File::class;
430
        $file = DataObject::get($class)->byId($fileId);
431
432
        if (!$file) {
433
            return new HTTPResponse(null, 404);
434
        }
435
436
        if (!$file->canView()) {
437
            return new HTTPResponse(null, 403);
438
        }
439
440
        $versions = Versioned::get_all_versions($class, $fileId)
441
            ->limit($this->config()->max_history_entries)
442
            ->sort('Version', 'DESC');
443
444
        $output = array();
445
        $next = array();
446
        $prev = null;
447
448
        // swap the order so we can get the version number to compare against.
449
        // i.e version 3 needs to know version 2 is the previous version
450
        $copy = $versions->map('Version', 'Version')->toArray();
451
        foreach (array_reverse($copy) as $k => $v) {
452
            if ($prev) {
453
                $next[$v] = $prev;
454
            }
455
456
            $prev = $v;
457
        }
458
459
        $_cachedMembers = array();
460
461
        foreach ($versions as $version) {
462
            $author = null;
463
464
            if ($version->AuthorID) {
465
                if (!isset($_cachedMembers[$version->AuthorID])) {
466
                    $_cachedMembers[$version->AuthorID] = DataObject::get(Member::class)
467
                        ->byId($version->AuthorID);
468
                }
469
470
                $author = $_cachedMembers[$version->AuthorID];
471
            }
472
473
            if ($version->canView()) {
474
                $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...
475
476
                if (isset($next[$version->Version])) {
477
                    $summary = $version->humanizedChanges(
478
                        $version->Version,
479
                        $next[$version->Version]
480
                    );
481
482
                    // if no summary returned by humanizedChanges, i.e we cannot work out what changed, just show a
483
                    // generic message
484
                    if (!$summary) {
485
                        $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.SAVEDFILE', "Saved file");
486
                    }
487
                } else {
488
                    $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UPLOADEDFILE', "Uploaded file");
489
                }
490
491
                $output[] = array(
492
                    'versionid' => $version->Version,
493
                    'date_ago' => $version->dbObject('LastEdited')->Ago(),
494
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
495
                    'status' => ($version->WasPublished) ? _t('File.PUBLISHED', 'Published') : '',
496
                    'author' => ($author)
497
                        ? $author->Name
498
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UNKNOWN', "Unknown"),
499
                    'summary' => ($summary)
500
                        ? $summary
501
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NOSUMMARY', "No summary available")
502
                );
503
            }
504
        }
505
506
        return
507
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
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
     * @param array $context
836
     * @return Form
837
     * @throws InvalidArgumentException
838
     */
839
    public function getFileHistoryForm($context)
840
    {
841
        // Check context
842
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
843
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
844
        }
845
        $id = $context['RecordID'];
846
        $versionId = $context['RecordVersion'];
847
        if (!$id || !$versionId) {
848
            return $this->httpError(404);
849
        }
850
851
        /** @var File $file */
852
        $file = Versioned::get_version(File::class, $id, $versionId);
853
        if (!$file) {
854
            return $this->httpError(404);
855
        }
856
857 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...
858
            $this->httpError(403, _t(
859
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
860
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
861
                '',
862
                ['ObjectTitle' => $file->i18n_singular_name()]
863
            ));
864
            return null;
865
        }
866
867
        $effectiveContext = array_merge($context, ['Record' => $file]);
868
        /** @var FormFactory $scaffolder */
869
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
870
        $form = $scaffolder->getForm($this, 'fileHistoryForm', $effectiveContext);
871
872
        // Configure form to respond to validation errors with form schema
873
        // if requested via react.
874 View Code Duplication
        $form->setValidationResponseCallback(function () use ($form, $id, $versionId) {
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...
875
            $schemaId = Controller::join_links($this->Link('schema/fileHistoryForm'), $id, $versionId);
876
            return $this->getSchemaResponse($form, $schemaId);
877
        });
878
879
880
        return $form;
881
    }
882
883
    /**
884
     * Gets a JSON schema representing the current edit form.
885
     *
886
     * WARNING: Experimental API.
887
     *
888
     * @param HTTPRequest $request
889
     * @return HTTPResponse
890
     */
891
    public function schema($request)
892
    {
893
        $formName = $request->param('FormName');
894
        if ($formName !== 'fileHistoryForm') {
895
            return parent::schema($request);
896
        }
897
898
        // Get schema for history form
899
        // @todo Eventually all form scaffolding will be based on context rather than record ID
900
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
901
        $itemID = $request->param('ItemID');
902
        $version = $request->param('OtherItemID');
903
        $form = $this->getFileHistoryForm([
904
            'RecordID' => $itemID,
905
            'RecordVersion' => $version,
906
        ]);
907
908
        // Respond with this schema
909
        $response = $this->getResponse();
910
        $response->addHeader('Content-Type', 'application/json');
911
        $response->setBody(Convert::raw2json($this->getSchemaForForm($form)));
0 ignored issues
show
Bug introduced by
It seems like $form defined by $this->getFileHistoryFor...dVersion' => $version)) on line 903 can be null; however, SilverStripe\Admin\LeftAndMain::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...
912
        return $response;
913
    }
914
915
    /**
916
     * Get file history form
917
     *
918
     * @return Form
919
     */
920
    public function fileHistoryForm()
921
    {
922
        $request = $this->getRequest();
923
        $id = $request->param('ID') ?: $request->postVar('ID');
924
        $version = $request->param('OtherID') ?: $request->postVar('Version');
925
        $form = $this->getFileHistoryForm([
926
            'RecordID' => $id,
927
            'RecordVersion' => $version,
928
        ]);
929
        return $form;
930
    }
931
932
    /**
933
     * @param array $data
934
     * @param Form $form
935
     * @return HTTPResponse
936
     */
937
    public function save($data, $form)
938
    {
939
        return $this->saveOrPublish($data, $form, false);
940
    }
941
942
    /**
943
     * @param array $data
944
     * @param Form $form
945
     * @return HTTPResponse
946
     */
947
    public function publish($data, $form)
948
    {
949
        return $this->saveOrPublish($data, $form, true);
950
    }
951
952
    /**
953
     * Update thisrecord
954
     *
955
     * @param array $data
956
     * @param Form $form
957
     * @param bool $doPublish
958
     * @return HTTPResponse
959
     */
960
    protected function saveOrPublish($data, $form, $doPublish = false)
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 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...
972
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
973
                ->addHeader('Content-Type', 'application/json');
974
        }
975
976
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
977
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
978
                ->addHeader('Content-Type', 'application/json');
979
        }
980
981
        $form->saveInto($record);
982
        $record->write();
983
984
        // Publish this record and owned objects
985
        if ($doPublish) {
986
            $record->publishRecursive();
987
        }
988
989
        // Return the record data in the same response as the schema to save a postback
990
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->exists() ? $record->ID : '');
991
        $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...
992
        $schemaData['record'] = $this->getObjectFromData($record);
993
        $response = new HTTPResponse(Convert::raw2json($schemaData));
994
        $response->addHeader('Content-Type', 'application/json');
995
        return $response;
996
    }
997
998
    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...
999
    {
1000 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...
1001
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
1002
                ->addHeader('Content-Type', 'application/json');
1003
        }
1004
1005
        $id = (int) $data['ID'];
1006
        /** @var File $record */
1007
        $record = $this->getList()->filter('ID', $id)->first();
1008
1009
        if (!$record) {
1010
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
1011
                ->addHeader('Content-Type', 'application/json');
1012
        }
1013
1014
        if (!$record->canUnpublish()) {
1015
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
1016
                ->addHeader('Content-Type', 'application/json');
1017
        }
1018
1019
        $record->doUnpublish();
1020
1021
        // Return the record data in the same response as the schema to save a postback
1022
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->exists() ? $record->ID : '');
1023
        $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...
1024
        $schemaData['record'] = $this->getObjectFromData($record);
1025
        $response = new HTTPResponse(Convert::raw2json($schemaData));
1026
        $response->addHeader('Content-Type', 'application/json');
1027
        return $response;
1028
    }
1029
1030
    /**
1031
     * @param File $file
1032
     *
1033
     * @return array
1034
     */
1035
    public function getObjectFromData(File $file)
1036
    {
1037
        $object = array(
1038
            'id' => $file->ID,
1039
            'created' => $file->Created,
1040
            'lastUpdated' => $file->LastEdited,
1041
            'owner' => null,
1042
            'parent' => null,
1043
            'title' => $file->Title,
1044
            'exists' => $file->exists(), // Broken file check
1045
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
1046
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
1047
            'name' => $file->Name,
1048
            'filename' => $file->Filename,
1049
            '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...
1050
            'size' => $file->Size,
1051
            'url' => $file->AbsoluteURL,
1052
            'published' => $file->isPublished(),
1053
            'modified' => $file->isModifiedOnDraft(),
1054
            'draft' => $file->isOnDraftOnly(),
1055
            'canEdit' => $file->canEdit(),
1056
            'canDelete' => $file->canArchive(),
1057
        );
1058
1059
        /** @var Member $owner */
1060
        $owner = $file->Owner();
1061
1062
        if ($owner) {
1063
            $object['owner'] = array(
1064
                'id' => $owner->ID,
1065
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1066
            );
1067
        }
1068
1069
        /** @var Folder $parent */
1070
        $parent = $file->Parent();
1071
1072
        if ($parent) {
1073
            $object['parent'] = array(
1074
                'id' => $parent->ID,
1075
                'title' => $parent->Title,
1076
                'filename' => $parent->Filename,
1077
            );
1078
        }
1079
1080
        /** @var File $file */
1081
        if ($file->getIsImage()) {
1082
            // Small thumbnail
1083
            $smallWidth = UploadField::config()->get('thumbnail_width');
1084
            $smallHeight = UploadField::config()->get('thumbnail_height');
1085
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1086
            if ($smallThumbnail && $smallThumbnail->exists()) {
1087
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1088
            }
1089
1090
            // Large thumbnail
1091
            $width = $this->config()->get('thumbnail_width');
1092
            $height = $this->config()->get('thumbnail_height');
1093
            $thumbnail = $file->FitMax($width, $height);
1094
            if ($thumbnail && $thumbnail->exists()) {
1095
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1096
            }
1097
            $object['dimensions']['width'] = $file->Width;
1098
            $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...
1099
        }
1100
1101
        return $object;
1102
    }
1103
1104
    /**
1105
     * Returns the files and subfolders contained in the currently selected folder,
1106
     * defaulting to the root node. Doubles as search results, if any search parameters
1107
     * are set through {@link SearchForm()}.
1108
     *
1109
     * @param array $params Unsanitised request parameters
1110
     * @return DataList
1111
     */
1112
    protected function getList($params = array())
1113
    {
1114
        $context = $this->getSearchContext();
1115
1116
        // Overwrite name filter to search both Name and Title attributes
1117
        $context->removeFilterByName('Name');
1118
1119
        // Lazy loaded list. Allows adding new filters through SearchContext.
1120
        /** @var DataList $list */
1121
        $list = $context->getResults($params);
1122
1123
        // Re-add previously removed "Name" filter as combined filter
1124
        // TODO Replace with composite SearchFilter once that API exists
1125
        if (!empty($params['Name'])) {
1126
            $list = $list->filterAny(array(
1127
                'Name:PartialMatch' => $params['Name'],
1128
                'Title:PartialMatch' => $params['Name']
1129
            ));
1130
        }
1131
1132
        // Optionally limit search to a folder (non-recursive)
1133
        if (!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1134
            $list = $list->filter('ParentID', $params['ParentID']);
1135
        }
1136
1137
        // Date filtering
1138 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...
1139
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1140
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1141
        }
1142 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...
1143
            $toDate = new DateField(null, null, $params['CreatedTo']);
1144
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1145
        }
1146
1147
        // Categories
1148
        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...
1149
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1150
            $list = $list->filter('Name:PartialMatch', $extensions);
1151
        }
1152
1153
        // Sort folders first
1154
        $list = $list->sort(
1155
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1156
        );
1157
1158
        // Pagination
1159
        if (isset($filters['page']) && isset($filters['limit'])) {
1160
            $page = $filters['page'];
1161
            $limit = $filters['limit'];
1162
            $offset = ($page - 1) * $limit;
1163
            $list = $list->limit($limit, $offset);
1164
        }
1165
1166
        // Access checks
1167
        $list = $list->filterByCallback(function (File $file) {
1168
            return $file->canView();
1169
        });
1170
1171
        return $list;
1172
    }
1173
1174
    /**
1175
     * Action handler for adding pages to a campaign
1176
     *
1177
     * @param array $data
1178
     * @param Form $form
1179
     * @return DBHTMLText|HTTPResponse
1180
     */
1181
    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...
1182
    {
1183
        $id = $data['ID'];
1184
        $record = $this->getList()->byID($id);
1185
1186
        $handler = AddToCampaignHandler::create($this, $record);
1187
        $results = $handler->addToCampaign($record, $data['Campaign']);
1188
        if (!isset($results)) {
1189
            return null;
1190
        }
1191
        $request = $this->getRequest();
1192
        if ($request->getHeader('X-Formschema-Request')) {
1193
            $data = $this->getSchemaForForm($handler->Form($record));
1194
            $data['message'] = $results;
1195
1196
            $response = new HTTPResponse(Convert::raw2json($data));
1197
            $response->addHeader('Content-Type', 'application/json');
1198
            return $response;
1199
        }
1200
        return $results;
1201
    }
1202
1203
    /**
1204
     * Url handler for add to campaign form
1205
     *
1206
     * @param HTTPRequest $request
1207
     * @return Form
1208
     */
1209
    public function addToCampaignForm($request)
1210
    {
1211
        // Get ID either from posted back value, or url parameter
1212
        $id = $request->param('ID') ?: $request->postVar('ID');
1213
        return $this->getAddToCampaignForm($id);
1214
    }
1215
1216
    /**
1217
     * @param int $id
1218
     * @return Form
1219
     */
1220
    public function getAddToCampaignForm($id)
1221
    {
1222
        // Get record-specific fields
1223
        $record = $this->getList()->byID($id);
1224
1225
        if (!$record) {
1226
            $this->httpError(404, _t(
1227
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1228
                'That {Type} couldn\'t be found',
1229
                '',
1230
                ['Type' => File::singleton()->i18n_singular_name()]
1231
            ));
1232
            return null;
1233
        }
1234 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...
1235
            $this->httpError(403, _t(
1236
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1237
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1238
                '',
1239
                ['ObjectTitle' => $record->i18n_singular_name()]
1240
            ));
1241
            return null;
1242
        }
1243
1244
        $handler = AddToCampaignHandler::create($this, $record);
1245
        $form = $handler->Form($record);
1246
1247 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...
1248
            $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1249
            return $this->getSchemaResponse($form, $schemaId);
1250
        });
1251
1252
        return $form;
1253
    }
1254
1255
    /**
1256
     * @return Upload
1257
     */
1258
    protected function getUpload()
1259
    {
1260
        $upload = Upload::create();
1261
        $upload->getValidator()->setAllowedExtensions(
1262
            // filter out '' since this would be a regex problem on JS end
1263
            array_filter(File::config()->get('allowed_extensions'))
1264
        );
1265
1266
        return $upload;
1267
    }
1268
}
1269