Completed
Push — master ( 3b23c0...1ceb95 )
by Damian
9s
created

AssetAdmin::getFileHistoryForm()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 43
Code Lines 24

Duplication

Lines 13
Ratio 30.23 %

Importance

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