Completed
Pull Request — master (#289)
by Ingo
01:56
created

AssetAdmin::publish()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

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

Loading history...
723
        if ($file instanceof Folder) {
724
            $name = FolderFormFactory::class;
725
        } elseif ($file instanceof Image) {
726
            $name = ImageFormFactory::class;
727
        } else {
728
            $name = FileFormFactory::class;
729
        }
730
        return Injector::inst()->get($name);
731
    }
732
733
    /**
734
     * The form is used to generate a form schema,
735
     * as well as an intermediary object to process data through API endpoints.
736
     * Since it's used directly on API endpoints, it does not have any form actions.
737
     * It handles both {@link File} and {@link Folder} records.
738
     *
739
     * @param int $id
740
     * @return Form
741
     */
742
    public function getfileEditForm($id)
743
    {
744
        /** @var File $file */
745
        $file = $this->getList()->byID($id);
746
747 View Code Duplication
        if (!$file->canView()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
748
            $this->httpError(403, _t(
749
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
750
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
751
                '',
752
                ['ObjectTitle' => $file->i18n_singular_name()]
753
            ));
754
            return null;
755
        }
756
757
        $scaffolder = $this->getFormFactory($file);
758
        $form = $scaffolder->getForm($this, 'fileEditForm', [
759
            'Record' => $file
760
        ]);
761
762
        // Configure form to respond to validation errors with form schema
763
        // if requested via react.
764 View Code Duplication
        $form->setValidationResponseCallback(function () use ($form, $file) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
765
            $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $file->exists() ? $file->ID : '');
766
            return $this->getSchemaResponse($form, $schemaId);
767
        });
768
769
        return $form;
770
    }
771
772
    /**
773
     * Get file edit form
774
     *
775
     * @return Form
776
     */
777 View Code Duplication
    public function fileEditForm()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
778
    {
779
        // Get ID either from posted back value, or url parameter
780
        $request = $this->getRequest();
781
        $id = $request->param('ID') ?: $request->postVar('ID');
782
        return $this->getfileEditForm($id);
783
    }
784
785
    /**
786
     * The form is used to generate a form schema,
787
     * as well as an intermediary object to process data through API endpoints.
788
     * Since it's used directly on API endpoints, it does not have any form actions.
789
     * It handles both {@link File} and {@link Folder} records.
790
     *
791
     * @param int $id
792
     * @return Form
793
     */
794
    public function getFileInsertForm($id)
795
    {
796
        /** @var File $file */
797
        $file = $this->getList()->byID($id);
798
799 View Code Duplication
        if (!$file->canView()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
800
            $this->httpError(403, _t(
801
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
802
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
803
                '',
804
                ['ObjectTitle' => $file->i18n_singular_name()]
805
            ));
806
            return null;
807
        }
808
809
        $scaffolder = $this->getFormFactory($file);
810
        $form = $scaffolder->getForm($this, 'fileInsertForm', [
811
            'Record' => $file,
812
            'Type' => 'insert',
813
        ]);
814
815
        return $form;
816
    }
817
818
    /**
819
     * Get file insert form
820
     *
821
     * @return Form
822
     */
823 View Code Duplication
    public function fileInsertForm()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
824
    {
825
        // Get ID either from posted back value, or url parameter
826
        $request = $this->getRequest();
827
        $id = $request->param('ID') ?: $request->postVar('ID');
828
        return $this->getFileInsertForm($id);
829
    }
830
831
    /**
832
     * @param array $context
833
     * @return Form
834
     * @throws InvalidArgumentException
835
     */
836
    public function getFileHistoryForm($context)
837
    {
838
        // Check context
839
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
840
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
841
        }
842
        $id = $context['RecordID'];
843
        $versionId = $context['RecordVersion'];
844
        if (!$id || !$versionId) {
845
            return $this->httpError(404);
846
        }
847
848
        /** @var File $file */
849
        $file = Versioned::get_version(File::class, $id, $versionId);
850
        if (!$file) {
851
            return $this->httpError(404);
852
        }
853
854 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...
855
            $this->httpError(403, _t(
856
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
857
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
858
                '',
859
                ['ObjectTitle' => $file->i18n_singular_name()]
860
            ));
861
            return null;
862
        }
863
864
        $effectiveContext = array_merge($context, ['Record' => $file]);
865
        /** @var FormFactory $scaffolder */
866
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
867
        $form = $scaffolder->getForm($this, 'fileHistoryForm', $effectiveContext);
868
869
        // Configure form to respond to validation errors with form schema
870
        // if requested via react.
871 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...
872
            $schemaId = Controller::join_links($this->Link('schema/fileHistoryForm'), $id, $versionId);
873
            return $this->getSchemaResponse($form, $schemaId);
874
        });
875
876
877
        return $form;
878
    }
879
880
    /**
881
     * Gets a JSON schema representing the current edit form.
882
     *
883
     * WARNING: Experimental API.
884
     *
885
     * @param HTTPRequest $request
886
     * @return HTTPResponse
887
     */
888
    public function schema($request)
889
    {
890
        $formName = $request->param('FormName');
891
        if ($formName !== 'fileHistoryForm') {
892
            return parent::schema($request);
893
        }
894
895
        // Get schema for history form
896
        // @todo Eventually all form scaffolding will be based on context rather than record ID
897
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
898
        $itemID = $request->param('ItemID');
899
        $version = $request->param('OtherItemID');
900
        $form = $this->getFileHistoryForm([
901
            'RecordID' => $itemID,
902
            'RecordVersion' => $version,
903
        ]);
904
905
        // Respond with this schema
906
        $response = $this->getResponse();
907
        $response->addHeader('Content-Type', 'application/json');
908
        $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 900 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...
909
        return $response;
910
    }
911
912
    /**
913
     * Get file history form
914
     *
915
     * @return Form
916
     */
917
    public function fileHistoryForm()
918
    {
919
        $request = $this->getRequest();
920
        $id = $request->param('ID') ?: $request->postVar('ID');
921
        $version = $request->param('OtherID') ?: $request->postVar('Version');
922
        $form = $this->getFileHistoryForm([
923
            'RecordID' => $id,
924
            'RecordVersion' => $version,
925
        ]);
926
        return $form;
927
    }
928
929
    /**
930
     * @param array $data
931
     * @param Form $form
932
     * @return HTTPResponse
933
     */
934
    public function save($data, $form)
935
    {
936
        return $this->saveOrPublish($data, $form, false);
937
    }
938
939
    /**
940
     * @param array $data
941
     * @param Form $form
942
     * @return HTTPResponse
943
     */
944
    public function publish($data, $form)
945
    {
946
        return $this->saveOrPublish($data, $form, true);
947
    }
948
949
    /**
950
     * Update thisrecord
951
     *
952
     * @param array $data
953
     * @param Form $form
954
     * @param bool $doPublish
955
     * @return HTTPResponse
956
     */
957
    protected function saveOrPublish($data, $form, $doPublish = false)
958
    {
959 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...
960
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
961
                ->addHeader('Content-Type', 'application/json');
962
        }
963
964
        $id = (int) $data['ID'];
965
        /** @var File $record */
966
        $record = $this->getList()->filter('ID', $id)->first();
967
968 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...
969
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
970
                ->addHeader('Content-Type', 'application/json');
971
        }
972
973
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
974
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
975
                ->addHeader('Content-Type', 'application/json');
976
        }
977
978
        $form->saveInto($record);
979
        $record->write();
980
981
        // Publish this record and owned objects
982
        if ($doPublish) {
983
            $record->publishRecursive();
984
        }
985
986
        // Return the record data in the same response as the schema to save a postback
987
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->exists() ? $record->ID : '');
988
        $schemaData = $this->getSchemaForForm($this->getfileEditForm($id), $schemaId);
0 ignored issues
show
Bug introduced by
It seems like $this->getfileEditForm($id) can be null; however, getSchemaForForm() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
989
        $schemaData['record'] = $this->getObjectFromData($record);
990
        $response = new HTTPResponse(Convert::raw2json($schemaData));
991
        $response->addHeader('Content-Type', 'application/json');
992
        return $response;
993
    }
994
995
    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...
996
    {
997 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...
998
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
999
                ->addHeader('Content-Type', 'application/json');
1000
        }
1001
1002
        $id = (int) $data['ID'];
1003
        /** @var File $record */
1004
        $record = $this->getList()->filter('ID', $id)->first();
1005
1006
        if (!$record) {
1007
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
1008
                ->addHeader('Content-Type', 'application/json');
1009
        }
1010
1011
        if (!$record->canUnpublish()) {
1012
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
1013
                ->addHeader('Content-Type', 'application/json');
1014
        }
1015
1016
        $record->doUnpublish();
1017
1018
        // Return the record data in the same response as the schema to save a postback
1019
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->exists() ? $record->ID : '');
1020
        $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...
1021
        $schemaData['record'] = $this->getObjectFromData($record);
1022
        $response = new HTTPResponse(Convert::raw2json($schemaData));
1023
        $response->addHeader('Content-Type', 'application/json');
1024
        return $response;
1025
    }
1026
1027
    /**
1028
     * @param File $file
1029
     *
1030
     * @return array
1031
     */
1032
    public function getObjectFromData(File $file)
1033
    {
1034
        $object = array(
1035
            'id' => $file->ID,
1036
            'created' => $file->Created,
1037
            'lastUpdated' => $file->LastEdited,
1038
            'owner' => null,
1039
            'parent' => null,
1040
            'title' => $file->Title,
1041
            'exists' => $file->exists(), // Broken file check
1042
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
1043
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
1044
            'name' => $file->Name,
1045
            'filename' => $file->Filename,
1046
            '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...
1047
            'size' => $file->Size,
1048
            'url' => $file->AbsoluteURL,
1049
            'published' => $file->isPublished(),
1050
            'modified' => $file->isModifiedOnDraft(),
1051
            'draft' => $file->isOnDraftOnly(),
1052
            'canEdit' => $file->canEdit(),
1053
            'canDelete' => $file->canArchive(),
1054
        );
1055
1056
        /** @var Member $owner */
1057
        $owner = $file->Owner();
1058
1059
        if ($owner) {
1060
            $object['owner'] = array(
1061
                'id' => $owner->ID,
1062
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1063
            );
1064
        }
1065
1066
        /** @var Folder $parent */
1067
        $parent = $file->Parent();
1068
1069
        if ($parent) {
1070
            $object['parent'] = array(
1071
                'id' => $parent->ID,
1072
                'title' => $parent->Title,
1073
                'filename' => $parent->Filename,
1074
            );
1075
        }
1076
1077
        /** @var File $file */
1078
        if ($file->getIsImage()) {
1079
            // Small thumbnail
1080
            $smallWidth = UploadField::config()->get('thumbnail_width');
1081
            $smallHeight = UploadField::config()->get('thumbnail_height');
1082
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1083
            if ($smallThumbnail && $smallThumbnail->exists()) {
1084
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1085
            }
1086
1087
            // Large thumbnail
1088
            $width = $this->config()->get('thumbnail_width');
1089
            $height = $this->config()->get('thumbnail_height');
1090
            $thumbnail = $file->FitMax($width, $height);
1091
            if ($thumbnail && $thumbnail->exists()) {
1092
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1093
            }
1094
            $object['dimensions']['width'] = $file->Width;
1095
            $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...
1096
        }
1097
1098
        return $object;
1099
    }
1100
1101
    /**
1102
     * Returns the files and subfolders contained in the currently selected folder,
1103
     * defaulting to the root node. Doubles as search results, if any search parameters
1104
     * are set through {@link SearchForm()}.
1105
     *
1106
     * @param array $params Unsanitised request parameters
1107
     * @return DataList
1108
     */
1109
    protected function getList($params = array())
1110
    {
1111
        $context = $this->getSearchContext();
1112
1113
        // Overwrite name filter to search both Name and Title attributes
1114
        $context->removeFilterByName('Name');
1115
1116
        // Lazy loaded list. Allows adding new filters through SearchContext.
1117
        /** @var DataList $list */
1118
        $list = $context->getResults($params);
1119
1120
        // Re-add previously removed "Name" filter as combined filter
1121
        // TODO Replace with composite SearchFilter once that API exists
1122
        if (!empty($params['Name'])) {
1123
            $list = $list->filterAny(array(
1124
                'Name:PartialMatch' => $params['Name'],
1125
                'Title:PartialMatch' => $params['Name']
1126
            ));
1127
        }
1128
1129
        // Optionally limit search to a folder (non-recursive)
1130
        if (!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
1131
            $list = $list->filter('ParentID', $params['ParentID']);
1132
        }
1133
1134
        // Date filtering
1135 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...
1136
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
1137
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
1138
        }
1139 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...
1140
            $toDate = new DateField(null, null, $params['CreatedTo']);
1141
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
1142
        }
1143
1144
        // Categories
1145
        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...
1146
            $extensions = File::config()->app_categories[$filters['AppCategory']];
1147
            $list = $list->filter('Name:PartialMatch', $extensions);
1148
        }
1149
1150
        // Sort folders first
1151
        $list = $list->sort(
1152
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
1153
        );
1154
1155
        // Pagination
1156
        if (isset($filters['page']) && isset($filters['limit'])) {
1157
            $page = $filters['page'];
1158
            $limit = $filters['limit'];
1159
            $offset = ($page - 1) * $limit;
1160
            $list = $list->limit($limit, $offset);
1161
        }
1162
1163
        // Access checks
1164
        $list = $list->filterByCallback(function (File $file) {
1165
            return $file->canView();
1166
        });
1167
1168
        return $list;
1169
    }
1170
1171
    /**
1172
     * Action handler for adding pages to a campaign
1173
     *
1174
     * @param array $data
1175
     * @param Form $form
1176
     * @return DBHTMLText|HTTPResponse
1177
     */
1178
    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...
1179
    {
1180
        $id = $data['ID'];
1181
        $record = $this->getList()->byID($id);
1182
1183
        $handler = AddToCampaignHandler::create($this, $record);
1184
        $results = $handler->addToCampaign($record, $data['Campaign']);
1185
        if (!isset($results)) {
1186
            return null;
1187
        }
1188
        $request = $this->getRequest();
1189
        if ($request->getHeader('X-Formschema-Request')) {
1190
            $data = $this->getSchemaForForm($handler->Form($record));
1191
            $data['message'] = $results;
1192
1193
            $response = new HTTPResponse(Convert::raw2json($data));
1194
            $response->addHeader('Content-Type', 'application/json');
1195
            return $response;
1196
        }
1197
        return $results;
1198
    }
1199
1200
    /**
1201
     * Url handler for add to campaign form
1202
     *
1203
     * @param HTTPRequest $request
1204
     * @return Form
1205
     */
1206
    public function addToCampaignForm($request)
1207
    {
1208
        // Get ID either from posted back value, or url parameter
1209
        $id = $request->param('ID') ?: $request->postVar('ID');
1210
        return $this->getAddToCampaignForm($id);
1211
    }
1212
1213
    /**
1214
     * @param int $id
1215
     * @return Form
1216
     */
1217
    public function getAddToCampaignForm($id)
1218
    {
1219
        // Get record-specific fields
1220
        $record = $this->getList()->byID($id);
1221
1222
        if (!$record) {
1223
            $this->httpError(404, _t(
1224
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1225
                'That {Type} couldn\'t be found',
1226
                '',
1227
                ['Type' => File::singleton()->i18n_singular_name()]
1228
            ));
1229
            return null;
1230
        }
1231 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...
1232
            $this->httpError(403, _t(
1233
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1234
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1235
                '',
1236
                ['ObjectTitle' => $record->i18n_singular_name()]
1237
            ));
1238
            return null;
1239
        }
1240
1241
        $handler = AddToCampaignHandler::create($this, $record);
1242
        $form = $handler->Form($record);
1243
1244 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...
1245
            $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1246
            return $this->getSchemaResponse($form, $schemaId);
1247
        });
1248
1249
        return $form;
1250
    }
1251
1252
    /**
1253
     * @return Upload
1254
     */
1255
    protected function getUpload()
1256
    {
1257
        $upload = Upload::create();
1258
        $upload->getValidator()->setAllowedExtensions(
1259
            // filter out '' since this would be a regex problem on JS end
1260
            array_filter(File::config()->get('allowed_extensions'))
1261
        );
1262
1263
        return $upload;
1264
    }
1265
}
1266