Completed
Pull Request — master (#323)
by Damian
01:59
created

AssetAdmin::FileSelectForm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 7
Ratio 100 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 7
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
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\AssetFormFactory;
10
use SilverStripe\AssetAdmin\Forms\UploadField;
11
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
12
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
13
use SilverStripe\AssetAdmin\Forms\FileHistoryFormFactory;
14
use SilverStripe\AssetAdmin\Forms\ImageFormFactory;
15
use SilverStripe\Assets\File;
16
use SilverStripe\Assets\Folder;
17
use SilverStripe\Assets\Image;
18
use SilverStripe\Assets\Storage\AssetNameGenerator;
19
use SilverStripe\Assets\Upload;
20
use SilverStripe\Control\Controller;
21
use SilverStripe\Control\HTTPRequest;
22
use SilverStripe\Control\HTTPResponse;
23
use SilverStripe\Core\Convert;
24
use SilverStripe\Core\Injector\Injector;
25
use SilverStripe\Forms\CheckboxField;
26
use SilverStripe\Forms\DateField;
27
use SilverStripe\Forms\DropdownField;
28
use SilverStripe\Forms\FieldGroup;
29
use SilverStripe\Forms\Form;
30
use SilverStripe\Forms\FormFactory;
31
use SilverStripe\Forms\HeaderField;
32
use SilverStripe\ORM\ArrayList;
33
use SilverStripe\ORM\DataList;
34
use SilverStripe\ORM\DataObject;
35
use SilverStripe\ORM\FieldType\DBHTMLText;
36
use SilverStripe\ORM\Search\SearchContext;
37
use SilverStripe\Security\Member;
38
use SilverStripe\Security\PermissionProvider;
39
use SilverStripe\Security\SecurityToken;
40
use SilverStripe\View\Requirements;
41
use SilverStripe\ORM\Versioning\Versioned;
42
43
/**
44
 * AssetAdmin is the 'file store' section of the CMS.
45
 * It provides an interface for manipulating the File and Folder objects in the system.
46
 */
47
class AssetAdmin extends LeftAndMain implements PermissionProvider
48
{
49
    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...
50
51
    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...
52
53
    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...
54
55
    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...
56
57
    private static $url_handlers = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_handlers is not used and could be removed.

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

Loading history...
58
        // Legacy redirect for SS3-style detail view
59
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
60
        // Pass all URLs to the index, for React to unpack
61
        'show/$FolderID/edit/$FileID' => 'index',
62
        // API access points with structured data
63
        'POST api/createFolder' => 'apiCreateFolder',
64
        'POST api/createFile' => 'apiCreateFile',
65
        'GET api/readFolder' => 'apiReadFolder',
66
        'PUT api/updateFolder' => 'apiUpdateFolder',
67
        'DELETE api/delete' => 'apiDelete',
68
        'GET api/search' => 'apiSearch',
69
        'GET api/history' => 'apiHistory'
70
    ];
71
72
    /**
73
     * Amount of results showing on a single page.
74
     *
75
     * @config
76
     * @var int
77
     */
78
    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...
79
80
    /**
81
     * @config
82
     * @see Upload->allowedMaxFileSize
83
     * @var int
84
     */
85
    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...
86
87
    /**
88
     * @config
89
     *
90
     * @var int
91
     */
92
    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...
93
94
    /**
95
     * @var array
96
     */
97
    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...
98
        'legacyRedirectForEditView',
99
        'apiCreateFolder',
100
        'apiCreateFile',
101
        'apiReadFolder',
102
        'apiUpdateFolder',
103
        'apiHistory',
104
        'apiDelete',
105
        'apiSearch',
106
        'FileEditForm',
107
        'FileHistoryForm',
108
        'AddToCampaignForm',
109
        'FileInsertForm',
110
        'schema',
111
        'FileSelectForm',
112
    );
113
114
    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...
115
116
    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...
117
118
    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...
119
120
    /**
121
     * Set up the controller
122
     */
123
    public function init()
124
    {
125
        parent::init();
126
127
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
128
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
129
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
130
131
        CMSBatchActionHandler::register(
132
            'delete',
133
            'SilverStripe\AssetAdmin\BatchAction\DeleteAssets',
134
            'SilverStripe\\Assets\\Folder'
135
        );
136
    }
137
138
    public function getClientConfig()
139
    {
140
        $baseLink = $this->Link();
141
        return array_merge( parent::getClientConfig(), [
142
            'reactRouter' => true,
143
            'createFileEndpoint' => [
144
                'url' => Controller::join_links($baseLink, 'api/createFile'),
145
                'method' => 'post',
146
                'payloadFormat' => 'urlencoded',
147
            ],
148
            'createFolderEndpoint' => [
149
                'url' => Controller::join_links($baseLink, 'api/createFolder'),
150
                'method' => 'post',
151
                'payloadFormat' => 'urlencoded',
152
            ],
153
            'readFolderEndpoint' => [
154
                'url' => Controller::join_links($baseLink, 'api/readFolder'),
155
                'method' => 'get',
156
                'responseFormat' => 'json',
157
            ],
158
            'searchEndpoint' => [
159
                'url' => Controller::join_links($baseLink, 'api/search'),
160
                'method' => 'get',
161
                'responseFormat' => 'json',
162
            ],
163
            'updateFolderEndpoint' => [
164
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
165
                'method' => 'put',
166
                'payloadFormat' => 'urlencoded',
167
            ],
168
            'deleteEndpoint' => [
169
                'url' => Controller::join_links($baseLink, 'api/delete'),
170
                'method' => 'delete',
171
                'payloadFormat' => 'urlencoded',
172
            ],
173
            'historyEndpoint' => [
174
                'url' => Controller::join_links($baseLink, 'api/history'),
175
                'method' => 'get',
176
                'responseFormat' => 'json'
177
            ],
178
            'limit' => $this->config()->page_length,
179
            'form' => [
180
                'FileEditForm' => [
181
                    'schemaUrl' => $this->Link('schema/FileEditForm')
182
                ],
183
                'FileInsertForm' => [
184
                    'schemaUrl' => $this->Link('schema/FileInsertForm')
185
                ],
186
                'FileSelectForm' => [
187
                    'schemaUrl' => $this->Link('schema/FileSelectForm')
188
                ],
189
                'AddToCampaignForm' => [
190
                    'schemaUrl' => $this->Link('schema/AddToCampaignForm')
191
                ],
192
                'FileHistoryForm' => [
193
                    'schemaUrl' => $this->Link('schema/FileHistoryForm')
194
                ]
195
            ],
196
        ]);
197
    }
198
199
    /**
200
     * Fetches a collection of files by ParentID.
201
     *
202
     * @param HTTPRequest $request
203
     * @return HTTPResponse
204
     */
205
    public function apiReadFolder(HTTPRequest $request)
206
    {
207
        $params = $request->requestVars();
208
        $items = array();
209
        $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...
210
        $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...
211
212
        if (!isset($params['id']) && !strlen($params['id'])) {
213
            $this->httpError(400);
214
        }
215
216
        $folderID = (int)$params['id'];
217
        /** @var Folder $folder */
218
        $folder = $folderID ? Folder::get()->byID($folderID) : Folder::singleton();
219
220
        if (!$folder) {
221
            $this->httpError(400);
222
        }
223
224
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
225
        $files = $this->getList()->filter('ParentID', $folderID);
226
227
        if ($files) {
228
            /** @var File $file */
229
            foreach ($files as $file) {
230
                if (!$file->canView()) {
231
                    continue;
232
                }
233
234
                $items[] = $this->getObjectFromData($file);
235
            }
236
        }
237
238
        // Build parents (for breadcrumbs)
239
        $parents = [];
240
        $next = $folder->Parent();
241
        while($next && $next->exists()) {
242
            array_unshift($parents, [
243
                'id' => $next->ID,
244
                'title' => $next->getTitle(),
245
                'filename' => $next->getFilename(),
246
            ]);
247
            if($next->ParentID) {
248
                $next = $next->Parent();
249
            } else {
250
                break;
251
            }
252
        }
253
254
        // Build response
255
        $response = new HTTPResponse();
256
        $response->addHeader('Content-Type', 'application/json');
257
        $response->setBody(json_encode([
258
            'files' => $items,
259
            'title' => $folder->getTitle(),
260
            'count' => count($items),
261
            'parents' => $parents,
262
            'parent' => $parents ? $parents[count($parents) - 1] : null,
263
            'parentID' => $folder->exists() ? $folder->ParentID : null, // grandparent
264
            'folderID' => $folderID,
265
            'canEdit' => $folder->canEdit(),
266
            'canDelete' => $folder->canArchive(),
267
        ]));
268
269
        return $response;
270
    }
271
272
    /**
273
     * @param HTTPRequest $request
274
     *
275
     * @return HTTPResponse
276
     */
277
    public function apiSearch(HTTPRequest $request)
278
    {
279
        $params = $request->getVars();
280
        $list = $this->getList($params);
281
282
        $response = new HTTPResponse();
283
        $response->addHeader('Content-Type', 'application/json');
284
        $response->setBody(json_encode([
285
            // Serialisation
286
            "files" => array_map(function($file) {
287
                return $this->getObjectFromData($file);
288
            }, $list->toArray()),
289
            "count" => $list->count(),
290
        ]));
291
292
        return $response;
293
    }
294
295
    /**
296
     * @param HTTPRequest $request
297
     *
298
     * @return HTTPResponse
299
     */
300
    public function apiDelete(HTTPRequest $request)
301
    {
302
        parse_str($request->getBody(), $vars);
303
304
        // CSRF check
305
        $token = SecurityToken::inst();
306 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...
307
            return new HTTPResponse(null, 400);
308
        }
309
310 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...
311
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
312
                ->addHeader('Content-Type', 'application/json');
313
        }
314
315
        $fileIds = $vars['ids'];
316
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
317
318 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...
319
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
320
                ->addHeader('Content-Type', 'application/json');
321
        }
322
323
        if (!min(array_map(function (File $file) {
324
            return $file->canArchive();
325
        }, $files))) {
326
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
327
                ->addHeader('Content-Type', 'application/json');
328
        }
329
330
        /** @var File $file */
331
        foreach ($files as $file) {
332
            $file->doArchive();
333
        }
334
335
        return (new HTTPResponse(json_encode(['status' => 'file was deleted'])))
336
            ->addHeader('Content-Type', 'application/json');
337
    }
338
339
    /**
340
     * Creates a single file based on a form-urlencoded upload.
341
     *
342
     * @param HTTPRequest $request
343
     * @return HTTPRequest|HTTPResponse
344
     */
345
    public function apiCreateFile(HTTPRequest $request)
346
    {
347
        $data = $request->postVars();
348
        $upload = $this->getUpload();
349
350
        // CSRF check
351
        $token = SecurityToken::inst();
352 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...
353
            return new HTTPResponse(null, 400);
354
        }
355
356
        // Check parent record
357
        /** @var Folder $parentRecord */
358
        $parentRecord = null;
359
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
360
            $parentRecord = Folder::get()->byID($data['ParentID']);
361
        }
362
        $data['Parent'] = $parentRecord;
363
364
        $tmpFile = $request->postVar('Upload');
365
        if(!$upload->validate($tmpFile)) {
366
            $result = ['message' => null];
367
            $errors = $upload->getErrors();
368
            if ($message = array_shift($errors)) {
369
                $result['message'] = [
370
                    'type' => 'error',
371
                    'value' => $message,
372
                ];
373
            }
374
            return (new HTTPResponse(json_encode($result), 400))
375
                ->addHeader('Content-Type', 'application/json');
376
        }
377
378
        // TODO Allow batch uploads
379
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
380
        /** @var File $file */
381
        $file = Injector::inst()->create($fileClass);
382
383
        // check canCreate permissions
384 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...
385
            $result = ['message' => [
386
                'type' => 'error',
387
                'value' => _t(
388
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
389
                    'You do not have permission to add files'
390
                )
391
            ]];
392
            return (new HTTPResponse(json_encode($result), 403))
393
                ->addHeader('Content-Type', 'application/json');
394
        }
395
396
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
397 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...
398
            $result = ['message' => [
399
                'type' => 'error',
400
                'value' => _t(
401
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
402
                    'Failed to load file'
403
                )
404
            ]];
405
            return (new HTTPResponse(json_encode($result), 400))
406
                ->addHeader('Content-Type', 'application/json');
407
        }
408
409
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
410
        $file->write();
411
412
        $result = [$this->getObjectFromData($file)];
413
414
        return (new HTTPResponse(json_encode($result)))
415
            ->addHeader('Content-Type', 'application/json');
416
    }
417
418
    /**
419
     * Returns a JSON array for history of a given file ID. Returns a list of all the history.
420
     *
421
     * @param HTTPRequest
422
     *
423
     * @return HTTPResponse
424
     */
425
    public function apiHistory(HTTPRequest $request)
426
    {
427
        // CSRF check not required as the GET request has no side effects.
428
        $fileId = $request->getVar('fileId');
429
430
        if(!$fileId || !is_numeric($fileId)) {
431
            return new HTTPResponse(null, 400);
432
        }
433
434
        $class = File::class;
435
        $file = DataObject::get($class)->byId($fileId);
436
437
        if(!$file) {
438
            return new HTTPResponse(null, 404);
439
        }
440
441
        if(!$file->canView()) {
442
            return new HTTPResponse(null, 403);
443
        }
444
445
        $versions = Versioned::get_all_versions($class, $fileId)
446
            ->limit($this->config()->max_history_entries)
447
            ->sort('Version', 'DESC');
448
449
        $output = array();
450
        $next = array();
451
        $prev = null;
452
453
        // swap the order so we can get the version number to compare against.
454
        // i.e version 3 needs to know version 2 is the previous version
455
        $copy = $versions->map('Version', 'Version')->toArray();
456
        foreach(array_reverse($copy) as $k => $v) {
457
            if($prev) {
458
                $next[$v] = $prev;
459
            }
460
461
            $prev = $v;
462
        }
463
464
        $_cachedMembers = array();
465
466
        foreach($versions as $version) {
467
            $author = null;
468
469
            if($version->AuthorID) {
470
                if(!isset($_cachedMembers[$version->AuthorID])) {
471
                    $_cachedMembers[$version->AuthorID] = DataObject::get(Member::class)
472
                        ->byId($version->AuthorID);
473
                }
474
475
                $author = $_cachedMembers[$version->AuthorID];
476
            }
477
478
            if($version->canView()) {
479
                $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...
480
481
                if(isset($next[$version->Version])) {
482
                    $summary = $version->humanizedChanges(
483
                        $version->Version,
484
                        $next[$version->Version]
485
                    );
486
487
                    // if no summary returned by humanizedChanges, i.e we cannot work out what changed, just show a
488
                    // generic message
489
                    if(!$summary) {
490
                        $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.SAVEDFILE', "Saved file");
491
                    }
492
                } else {
493
                    $summary = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UPLOADEDFILE', "Uploaded file");
494
                }
495
496
                $output[] = array(
497
                    'versionid' => $version->Version,
498
                    'date_ago' => $version->dbObject('LastEdited')->Ago(),
499
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
500
                    'status' => ($version->WasPublished) ? _t('File.PUBLISHED', 'Published') : '',
501
                    'author' => ($author)
502
                        ? $author->Name
503
                        : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UNKNOWN', "Unknown"),
504
                    'summary' => ($summary) ? $summary : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NOSUMMARY', "No summary available")
505
                );
506
            }
507
        }
508
509
        return
510
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
511
512
    }
513
514
515
    /**
516
     * Creates a single folder, within an optional parent folder.
517
     *
518
     * @param HTTPRequest $request
519
     * @return HTTPRequest|HTTPResponse
520
     */
521
    public function apiCreateFolder(HTTPRequest $request)
522
    {
523
        $data = $request->postVars();
524
525
        $class = 'SilverStripe\\Assets\\Folder';
526
527
        // CSRF check
528
        $token = SecurityToken::inst();
529 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...
530
            return new HTTPResponse(null, 400);
531
        }
532
533
        // check addchildren permissions
534
        /** @var Folder $parentRecord */
535
        $parentRecord = null;
536
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
537
            $parentRecord = DataObject::get_by_id($class, $data['ParentID']);
538
        }
539
        $data['Parent'] = $parentRecord;
540
        $data['ParentID'] = $parentRecord ? (int)$parentRecord->ID : 0;
541
542
        // Build filename
543
        $baseFilename = isset($data['Name'])
544
            ? basename($data['Name'])
545
            : _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.NEWFOLDER', "NewFolder");
546
547
        if ($parentRecord && $parentRecord->ID) {
548
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
549
        }
550
551
        // Ensure name is unique
552
        $nameGenerator = $this->getNameGenerator($baseFilename);
553
        $filename = null;
554
        foreach ($nameGenerator as $filename) {
555
            if (! File::find($filename)) {
556
                break;
557
            }
558
        }
559
        $data['Name'] = basename($filename);
560
561
        // Create record
562
        /** @var Folder $record */
563
        $record = Injector::inst()->create($class);
564
565
        // check create permissions
566
        if (!$record->canCreate(null, $data)) {
567
            return (new HTTPResponse(null, 403))
568
                ->addHeader('Content-Type', 'application/json');
569
        }
570
571
        $record->ParentID = $data['ParentID'];
572
        $record->Name = $record->Title = basename($data['Name']);
573
        $record->write();
574
575
        $result = $this->getObjectFromData($record);
576
577
        return (new HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
578
    }
579
580
    /**
581
     * Redirects 3.x style detail links to new 4.x style routing.
582
     *
583
     * @param HTTPRequest $request
584
     */
585
    public function legacyRedirectForEditView($request)
586
    {
587
        $fileID = $request->param('FileID');
588
        /** @var File $file */
589
        $file = File::get()->byID($fileID);
590
        $link = $this->getFileEditLink($file) ?: $this->Link();
591
        $this->redirect($link);
592
    }
593
594
    /**
595
     * Given a file return the CMS link to edit it
596
     *
597
     * @param File $file
598
     * @return string
599
     */
600
    public function getFileEditLink($file)
601
    {
602
        if(!$file || !$file->isInDB()) {
603
            return null;
604
        }
605
606
        return Controller::join_links(
607
            $this->Link('show'),
608
            $file->ParentID,
609
            'edit',
610
            $file->ID
611
        );
612
    }
613
614
    /**
615
     * Get the search context from {@link File}, used to create the search form
616
     * as well as power the /search API endpoint.
617
     *
618
     * @return SearchContext
619
     */
620
    public function getSearchContext()
621
    {
622
        $context = File::singleton()->getDefaultSearchContext();
623
624
        // Customize fields
625
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
626
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
627
        ->setConfig('showcalendar', true);
628
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
629
        ->setConfig('showcalendar', true);
630
        $dateGroup = FieldGroup::create(
631
            $dateHeader,
632
            $dateFrom,
633
            $dateTo
634
        );
635
        $context->addField($dateGroup);
636
        /** @skipUpgrade */
637
        $appCategories = array(
638
            'archive' => _t(
639
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryArchive',
640
                'Archive'
641
            ),
642
            'audio' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryAudio', 'Audio'),
643
            'document' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryDocument', 'Document'),
644
            'flash' => _t(
645
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryFlash',
646
                'Flash',
647
                'The fileformat'
648
            ),
649
            'image' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryImage', 'Image'),
650
            'video' => _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.AppCategoryVideo', 'Video'),
651
        );
652
        $context->addField(
653
            $typeDropdown = new DropdownField(
654
                'AppCategory',
655
                _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.Filetype', 'File type'),
656
                $appCategories
657
            )
658
        );
659
660
        $typeDropdown->setEmptyString(' ');
661
662
        $currentfolderLabel = _t(
663
            'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CurrentFolderOnly',
664
            'Limit to current folder?'
665
        );
666
        $context->addField(
667
            new CheckboxField('CurrentFolderOnly', $currentfolderLabel)
668
        );
669
        $context->getFields()->removeByName('Title');
670
671
        return $context;
672
    }
673
674
    /**
675
     * Get an asset renamer for the given filename.
676
     *
677
     * @param  string             $filename Path name
678
     * @return AssetNameGenerator
679
     */
680
    protected function getNameGenerator($filename)
681
    {
682
        return Injector::inst()
683
            ->createWithArgs('AssetNameGenerator', array($filename));
684
    }
685
686
    /**
687
     * @todo Implement on client
688
     *
689
     * @param bool $unlinked
690
     * @return ArrayList
691
     */
692
    public function breadcrumbs($unlinked = false)
693
    {
694
        return null;
695
    }
696
697
698
    /**
699
     * Don't include class namespace in auto-generated CSS class
700
     */
701
    public function baseCSSClasses()
702
    {
703
        return 'AssetAdmin LeftAndMain';
704
    }
705
706
    public function providePermissions()
707
    {
708
        return array(
709
            "CMS_ACCESS_AssetAdmin" => array(
710
                '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...
711
                    'title' => static::menu_title()
712
                )),
713
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
714
            )
715
        );
716
    }
717
718
    /**
719
     * Build a form scaffolder for this model
720
     *
721
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
722
     *
723
     * @param File $file
724
     * @return FormFactory
725
     */
726
    public function getFormFactory(File $file)
727
    {
728
        // Get service name based on file class
729
        $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...
730
        if ($file instanceof Folder) {
731
            $name = FolderFormFactory::class;
732
        } elseif ($file instanceof Image) {
733
            $name = ImageFormFactory::class;
734
        } else {
735
            $name = FileFormFactory::class;
736
        }
737
        return Injector::inst()->get($name);
738
    }
739
740
    /**
741
     * The form is used to generate a form schema,
742
     * as well as an intermediary object to process data through API endpoints.
743
     * Since it's used directly on API endpoints, it does not have any form actions.
744
     * It handles both {@link File} and {@link Folder} records.
745
     *
746
     * @param int $id
747
     * @return Form
748
     */
749
    public function getFileEditForm($id)
750
    {
751
        return $this->getAbstractFileForm($id, 'FileEditForm');
752
        }
753
754
    /**
755
     * Get file edit form
756
     *
757
     * @return Form
758
     */
759 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...
760
    {
761
        // Get ID either from posted back value, or url parameter
762
        $request = $this->getRequest();
763
        $id = $request->param('ID') ?: $request->postVar('ID');
764
        return $this->getFileEditForm($id);
765
    }
766
767
    /**
768
     * The form is used to generate a form schema,
769
     * as well as an intermediary object to process data through API endpoints.
770
     * Since it's used directly on API endpoints, it does not have any form actions.
771
     * It handles both {@link File} and {@link Folder} records.
772
     *
773
     * @param int $id
774
     * @return Form
775
     */
776
    public function getFileInsertForm($id)
777
    {
778
        return $this->getAbstractFileForm($id, 'FileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT ]);
779
    }
780
781
    /**
782
     * Get file insert form
783
     *
784
     * @return Form
785
     */
786 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...
787
    {
788
        // Get ID either from posted back value, or url parameter
789
        $request = $this->getRequest();
790
        $id = $request->param('ID') ?: $request->postVar('ID');
791
        return $this->getFileInsertForm($id);
792
    }
793
794
    /**
795
     * Abstract method for generating a form for a file
796
     *
797
     * @param int $id Record ID
798
     * @param string $name Form name
799
     * @param array $context Form context
800
     * @return Form
801
     */
802
    protected function getAbstractFileForm($id, $name, $context = [])
803
    {
804
        /** @var File $file */
805
        $file = $this->getList()->byID($id);
806
807 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...
808
            $this->httpError(403, _t(
809
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
810
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
811
                '',
812
                ['ObjectTitle' => $file->i18n_singular_name()]
813
            ));
814
            return null;
815
        }
816
817
        // Pass to form factory
818
        $augmentedContext = array_merge($context, ['Record' => $file]);
819
        $scaffolder = $this->getFormFactory($file);
820
        $form = $scaffolder->getForm($this, $name, $augmentedContext);
821
822
        // Configure form to respond to validation errors with form schema
823
        // if requested via react.
824
        $form->setValidationResponseCallback(function() use ($form, $file, $name) {
825
            $schemaId = Controller::join_links(
826
                $this->Link('schema'),
827
                $name,
828
                $file->isInDB() ? $file->ID : ''
829
            );
830
            return $this->getSchemaResponse($form, $schemaId);
831
        });
832
833
        return $form;
834
    }
835
836 View Code Duplication
    public function FileSelectForm()
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...
837
    {
838
        // Get ID either from posted back value, or url parameter
839
        $request = $this->getRequest();
840
        $id = $request->param('ID') ?: $request->postVar('ID');
841
        return $this->getFileSelectForm($id);
842
    }
843
844
    public function getFileSelectForm($id) {
845
        return $this->getAbstractFileForm($id, 'FileSelectForm', [ 'Type' => AssetFormFactory::TYPE_SELECT ]);
846
    }
847
848
    /**
849
     * @param array $context
850
     * @return Form
851
     * @throws InvalidArgumentException
852
     */
853
    public function getFileHistoryForm($context)
854
    {
855
        // Check context
856
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
857
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
858
        }
859
        $id = $context['RecordID'];
860
        $versionId = $context['RecordVersion'];
861
        if(!$id || !$versionId) {
862
            return $this->httpError(404);
863
        }
864
865
        /** @var File $file */
866
        $file = Versioned::get_version(File::class, $id, $versionId);
867
        if (!$file) {
868
            return $this->httpError(404);
869
        }
870
871 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...
872
            $this->httpError(403, _t(
873
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
874
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
875
                '',
876
                ['ObjectTitle' => $file->i18n_singular_name()]
877
            ));
878
            return null;
879
        }
880
881
        $effectiveContext = array_merge($context, ['Record' => $file]);
882
        /** @var FormFactory $scaffolder */
883
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
884
        $form = $scaffolder->getForm($this, 'FileHistoryForm', $effectiveContext);
885
886
        // Configure form to respond to validation errors with form schema
887
        // if requested via react.
888 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...
889
            $schemaId = Controller::join_links($this->Link('schema/FileHistoryForm'), $id, $versionId);
890
            return $this->getSchemaResponse($form, $schemaId);
891
        });
892
893
894
        return $form;
895
    }
896
897
    /**
898
	 * Gets a JSON schema representing the current edit form.
899
	 *
900
	 * WARNING: Experimental API.
901
	 *
902
	 * @param HTTPRequest $request
903
	 * @return HTTPResponse
904
	 */
905
	public function schema($request) {
906
		$formName = $request->param('FormName');
907
        if ($formName !== 'FileHistoryForm') {
908
            return parent::schema($request);
909
        }
910
911
        // Get schema for history form
912
        // @todo Eventually all form scaffolding will be based on context rather than record ID
913
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
914
		$itemID = $request->param('ItemID');
915
        $version = $request->param('OtherItemID');
916
        $form = $this->getFileHistoryForm([
917
            'RecordID' => $itemID,
918
            'RecordVersion' => $version,
919
        ]);
920
921
        // Respond with this schema
922
		$response = $this->getResponse();
923
        $response->addHeader('Content-Type', 'application/json');
924
        $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 916 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...
925
        return $response;
926
	}
927
928
    /**
929
     * Get file history form
930
     *
931
     * @return Form
932
     */
933
    public function FileHistoryForm()
934
    {
935
        $request = $this->getRequest();
936
        $id = $request->param('ID') ?: $request->postVar('ID');
937
        $version = $request->param('OtherID') ?: $request->postVar('Version');
938
        $form = $this->getFileHistoryForm([
939
            'RecordID' => $id,
940
            'RecordVersion' => $version,
941
        ]);
942
        return $form;
943
    }
944
945
    /**
946
     * @param array $data
947
     * @param Form $form
948
     * @return HTTPResponse
949
     */
950
    public function save($data, $form)
951
    {
952
        return $this->saveOrPublish($data, $form, false);
953
    }
954
955
    /**
956
     * @param array $data
957
     * @param Form $form
958
     * @return HTTPResponse
959
     */
960
    public function publish($data, $form)
961
    {
962
        return $this->saveOrPublish($data, $form, true);
963
    }
964
965
    /**
966
     * Update thisrecord
967
     *
968
     * @param array $data
969
     * @param Form $form
970
     * @param bool $doPublish
971
     * @return HTTPResponse
972
     */
973
    protected function saveOrPublish($data, $form, $doPublish = false)
974
    {
975 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...
976
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
977
                ->addHeader('Content-Type', 'application/json');
978
        }
979
980
        $id = (int) $data['ID'];
981
        /** @var File $record */
982
        $record = $this->getList()->filter('ID', $id)->first();
983
984 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...
985
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
986
                ->addHeader('Content-Type', 'application/json');
987
        }
988
989
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
990
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
991
                ->addHeader('Content-Type', 'application/json');
992
        }
993
994
        $form->saveInto($record);
995
        $record->write();
996
997
        // Publish this record and owned objects
998
        if ($doPublish) {
999
            $record->publishRecursive();
1000
        }
1001
1002
        // Return the record data in the same response as the schema to save a postback
1003
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
1004
        $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...
1005
        $schemaData['record'] = $this->getObjectFromData($record);
1006
        $response = new HTTPResponse(Convert::raw2json($schemaData));
1007
        $response->addHeader('Content-Type', 'application/json');
1008
        return $response;
1009
    }
1010
1011
    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...
1012
    {
1013 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...
1014
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
1015
                ->addHeader('Content-Type', 'application/json');
1016
        }
1017
1018
        $id = (int) $data['ID'];
1019
        /** @var File $record */
1020
        $record = $this->getList()->filter('ID', $id)->first();
1021
1022
        if (!$record) {
1023
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
1024
                ->addHeader('Content-Type', 'application/json');
1025
        }
1026
1027
        if (!$record->canUnpublish()) {
1028
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
1029
                ->addHeader('Content-Type', 'application/json');
1030
        }
1031
1032
        $record->doUnpublish();
1033
1034
        // Return the record data in the same response as the schema to save a postback
1035
        $schemaId = Controller::join_links($this->Link('schema/FileEditForm'), $record->exists() ? $record->ID : '');
1036
        $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...
1037
        $schemaData['record'] = $this->getObjectFromData($record);
1038
        $response = new HTTPResponse(Convert::raw2json($schemaData));
1039
        $response->addHeader('Content-Type', 'application/json');
1040
        return $response;
1041
    }
1042
1043
    /**
1044
     * @param File $file
1045
     *
1046
     * @return array
1047
     */
1048
    public function getObjectFromData(File $file)
1049
    {
1050
        $object = array(
1051
            'id' => $file->ID,
1052
            'created' => $file->Created,
1053
            'lastUpdated' => $file->LastEdited,
1054
            'owner' => null,
1055
            'parent' => null,
1056
            'title' => $file->Title,
1057
            'exists' => $file->exists(), // Broken file check
1058
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
1059
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
1060
            'name' => $file->Name,
1061
            'filename' => $file->Filename,
1062
            '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...
1063
            'size' => $file->Size,
1064
            'url' => $file->AbsoluteURL,
1065
            'published' => $file->isPublished(),
1066
            'modified' => $file->isModifiedOnDraft(),
1067
            'draft' => $file->isOnDraftOnly(),
1068
            'canEdit' => $file->canEdit(),
1069
            'canDelete' => $file->canArchive(),
1070
        );
1071
1072
        /** @var Member $owner */
1073
        $owner = $file->Owner();
1074
1075
        if ($owner) {
1076
            $object['owner'] = array(
1077
                'id' => $owner->ID,
1078
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1079
            );
1080
        }
1081
1082
        /** @var Folder $parent */
1083
        $parent = $file->Parent();
1084
1085
        if ($parent) {
1086
            $object['parent'] = array(
1087
                'id' => $parent->ID,
1088
                'title' => $parent->Title,
1089
                'filename' => $parent->Filename,
1090
            );
1091
        }
1092
1093
        /** @var File $file */
1094
        if ($file->getIsImage()) {
1095
            // Small thumbnail
1096
            $smallWidth = UploadField::config()->get('thumbnail_width');
1097
            $smallHeight = UploadField::config()->get('thumbnail_height');
1098
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1099
            if ($smallThumbnail && $smallThumbnail->exists()) {
1100
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1101
            }
1102
1103
            // Large thumbnail
1104
            $width = $this->config()->get('thumbnail_width');
1105
            $height = $this->config()->get('thumbnail_height');
1106
            $thumbnail = $file->FitMax($width, $height);
1107
            if ($thumbnail && $thumbnail->exists()) {
1108
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1109
            }
1110
            $object['dimensions']['width'] = $file->Width;
1111
            $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...
1112
        }
1113
1114
        return $object;
1115
    }
1116
1117
    /**
1118
     * Returns the files and subfolders contained in the currently selected folder,
1119
     * defaulting to the root node. Doubles as search results, if any search parameters
1120
     * are set through {@link SearchForm()}.
1121
     *
1122
     * @param array $params Unsanitised request parameters
1123
     * @return DataList
1124
     */
1125
    protected function getList($params = array())
1126
    {
1127
        $context = $this->getSearchContext();
1128
1129
        // Overwrite name filter to search both Name and Title attributes
1130
        $context->removeFilterByName('Name');
1131
1132
        // Lazy loaded list. Allows adding new filters through SearchContext.
1133
        /** @var DataList $list */
1134
        $list = $context->getResults($params);
1135
1136
        // Re-add previously removed "Name" filter as combined filter
1137
        // TODO Replace with composite SearchFilter once that API exists
1138
        if(!empty($params['Name'])) {
1139
            $list = $list->filterAny(array(
1140
                'Name:PartialMatch' => $params['Name'],
1141
                'Title:PartialMatch' => $params['Name']
1142
            ));
1143
        }
1144
1145
        // Optionally limit search to a folder (non-recursive)
1146
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1147
            $list = $list->filter('ParentID', $params['ParentID']);
1148
        }
1149
1150
        // Date filtering
1151 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...
1152
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1153
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1154
        }
1155 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...
1156
            $toDate = new DateField(null, null, $params['CreatedTo']);
1157
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1158
        }
1159
1160
        // Categories
1161
        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...
1162
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1163
            $list = $list->filter('Name:PartialMatch', $extensions);
1164
        }
1165
1166
        // Sort folders first
1167
        $list = $list->sort(
1168
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1169
        );
1170
1171
        // Pagination
1172
        if (isset($filters['page']) && isset($filters['limit'])) {
1173
            $page = $filters['page'];
1174
            $limit = $filters['limit'];
1175
            $offset = ($page - 1) * $limit;
1176
            $list = $list->limit($limit, $offset);
1177
        }
1178
1179
        // Access checks
1180
        $list = $list->filterByCallback(function(File $file) {
1181
            return $file->canView();
1182
        });
1183
1184
        return $list;
1185
    }
1186
1187
    /**
1188
     * Action handler for adding pages to a campaign
1189
     *
1190
     * @param array $data
1191
     * @param Form $form
1192
     * @return DBHTMLText|HTTPResponse
1193
     */
1194
    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...
1195
    {
1196
        $id = $data['ID'];
1197
        $record = $this->getList()->byID($id);
1198
1199
        $handler = AddToCampaignHandler::create($this, $record);
1200
        $results = $handler->addToCampaign($record, $data['Campaign']);
1201
        if (!isset($results)) {
1202
            return null;
1203
        }
1204
        $request = $this->getRequest();
1205
        if($request->getHeader('X-Formschema-Request')) {
1206
            $data = $this->getSchemaForForm($handler->Form($record));
1207
            $data['message'] = $results;
1208
1209
            $response = new HTTPResponse(Convert::raw2json($data));
1210
            $response->addHeader('Content-Type', 'application/json');
1211
            return $response;
1212
        }
1213
        return $results;
1214
    }
1215
1216
    /**
1217
     * Url handler for add to campaign form
1218
     *
1219
     * @param HTTPRequest $request
1220
     * @return Form
1221
     */
1222
    public function AddToCampaignForm($request)
1223
    {
1224
        // Get ID either from posted back value, or url parameter
1225
        $id = $request->param('ID') ?: $request->postVar('ID');
1226
        return $this->getAddToCampaignForm($id);
1227
    }
1228
1229
    /**
1230
     * @param int $id
1231
     * @return Form
1232
     */
1233
    public function getAddToCampaignForm($id)
1234
    {
1235
        // Get record-specific fields
1236
        $record = $this->getList()->byID($id);
1237
1238
        if (!$record) {
1239
            $this->httpError(404, _t(
1240
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1241
                'That {Type} couldn\'t be found',
1242
                '',
1243
                ['Type' => File::singleton()->i18n_singular_name()]
1244
            ));
1245
            return null;
1246
        }
1247 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...
1248
            $this->httpError(403, _t(
1249
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1250
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1251
                '',
1252
                ['ObjectTitle' => $record->i18n_singular_name()]
1253
            ));
1254
            return null;
1255
        }
1256
1257
        $handler = AddToCampaignHandler::create($this, $record);
1258
        $form = $handler->Form($record);
1259
1260 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...
1261
            $schemaId = Controller::join_links($this->Link('schema/AddToCampaignForm'), $id);
1262
            return $this->getSchemaResponse($form, $schemaId);
1263
        });
1264
1265
        return $form;
1266
    }
1267
1268
    /**
1269
     * @return Upload
1270
     */
1271
    protected function getUpload()
1272
    {
1273
        $upload = Upload::create();
1274
        $upload->getValidator()->setAllowedExtensions(
1275
            // filter out '' since this would be a regex problem on JS end
1276
            array_filter(File::config()->get('allowed_extensions'))
1277
        );
1278
1279
        return $upload;
1280
    }
1281
}
1282