Completed
Pull Request — master (#499)
by
unknown
02:35
created

AssetAdmin::fileEditorLinkForm()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 14
Ratio 100 %

Importance

Changes 0
Metric Value
dl 14
loc 14
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 1
1
<?php
2
3
namespace SilverStripe\AssetAdmin\Controller;
4
5
use InvalidArgumentException;
6
use SilverStripe\AssetAdmin\Forms\FolderCreateFormFactory;
7
use SilverStripe\AssetAdmin\Forms\FolderFormFactory;
8
use SilverStripe\Admin\LeftAndMainFormRequestHandler;
9
use SilverStripe\CampaignAdmin\AddToCampaignHandler;
10
use SilverStripe\Admin\CMSBatchActionHandler;
11
use SilverStripe\Admin\LeftAndMain;
12
use SilverStripe\AssetAdmin\BatchAction\DeleteAssets;
13
use SilverStripe\AssetAdmin\Forms\AssetFormFactory;
14
use SilverStripe\AssetAdmin\Forms\FileSearchFormFactory;
15
use SilverStripe\AssetAdmin\Forms\UploadField;
16
use SilverStripe\AssetAdmin\Forms\FileFormFactory;
17
use SilverStripe\AssetAdmin\Forms\FileHistoryFormFactory;
18
use SilverStripe\AssetAdmin\Forms\ImageFormFactory;
19
use SilverStripe\Assets\File;
20
use SilverStripe\Assets\Folder;
21
use SilverStripe\Assets\Image;
22
use SilverStripe\Assets\Storage\AssetNameGenerator;
23
use SilverStripe\Assets\Upload;
24
use SilverStripe\Control\Controller;
25
use SilverStripe\Control\HTTPRequest;
26
use SilverStripe\Control\HTTPResponse;
27
use SilverStripe\Control\RequestHandler;
28
use SilverStripe\Core\Injector\Injector;
29
use SilverStripe\Core\Manifest\ModuleLoader;
30
use SilverStripe\Forms\Form;
31
use SilverStripe\Forms\FormFactory;
32
use SilverStripe\ORM\ArrayList;
33
use SilverStripe\ORM\DataObject;
34
use SilverStripe\ORM\FieldType\DBHTMLText;
35
use SilverStripe\ORM\ValidationResult;
36
use SilverStripe\Security\Member;
37
use SilverStripe\Security\PermissionProvider;
38
use SilverStripe\Security\SecurityToken;
39
use SilverStripe\View\Requirements;
40
use SilverStripe\Versioned\Versioned;
41
use Exception;
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
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $menu_title is not used and could be removed.

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

Loading history...
54
55
    private static $menu_icon_class = 'font-icon-image';
0 ignored issues
show
Unused Code introduced by
The property $menu_icon_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 $tree_class = Folder::class;
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...
58
59
    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...
60
        // Legacy redirect for SS3-style detail view
61
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
62
        // Pass all URLs to the index, for React to unpack
63
        'show/$FolderID/edit/$FileID' => 'index',
64
        // API access points with structured data
65
        'POST api/createFile' => 'apiCreateFile',
66
        'POST api/uploadFile' => 'apiUploadFile',
67
        'GET api/history' => 'apiHistory',
68
        'fileEditForm/$ID' => 'fileEditForm',
69
        'fileInsertForm/$ID' => 'fileInsertForm',
70
        'fileUpdateForm/$ID' => 'fileUpdateForm',
71
        'fileEditorLinkForm/$ID' => 'fileEditorLinkForm',
72
        'fileUpdateEditorLinkForm/$ID' => 'fileUpdateEditorLinkForm',
73
        'fileHistoryForm/$ID/$VersionID' => 'fileHistoryForm',
74
        'folderCreateForm/$ParentID' => 'folderCreateForm',
75
    ];
76
77
    /**
78
     * Amount of results showing on a single page.
79
     *
80
     * @config
81
     * @var int
82
     */
83
    private static $page_length = 50;
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...
84
85
    /**
86
     * @config
87
     * @see Upload->allowedMaxFileSize
88
     * @var int
89
     */
90
    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...
91
92
    /**
93
     * @config
94
     *
95
     * @var int
96
     */
97
    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...
98
99
    /**
100
     * @var array
101
     */
102
    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...
103
        'legacyRedirectForEditView',
104
        'apiCreateFile',
105
        'apiUploadFile',
106
        'apiHistory',
107
        'folderCreateForm',
108
        'fileEditForm',
109
        'fileHistoryForm',
110
        'addToCampaignForm',
111
        'fileInsertForm',
112
        'fileUpdateForm',
113
        'fileEditorLinkForm',
114
        'fileUpdateEditorLinkForm',
115
        'schema',
116
        'fileSelectForm',
117
        'fileSearchForm',
118
    );
119
120
    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...
121
122
    /**
123
     * Retina thumbnail image (native size: 176)
124
     *
125
     * @config
126
     * @var int
127
     */
128
    private static $thumbnail_width = 352;
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...
129
130
    /**
131
     * Retina thumbnail height (native size: 132)
132
     *
133
     * @config
134
     * @var int
135
     */
136
    private static $thumbnail_height = 264;
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...
137
138
    /**
139
     * Safely limit max inline thumbnail size to 200kb
140
     *
141
     * @config
142
     * @var int
143
     */
144
    private static $max_thumbnail_bytes = 200000;
0 ignored issues
show
Unused Code introduced by
The property $max_thumbnail_bytes 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...
145
146
    /**
147
     * Set up the controller
148
     */
149
    public function init()
150
    {
151
        parent::init();
152
153
        $module = ModuleLoader::getModule('silverstripe/asset-admin');
154
        Requirements::add_i18n_javascript($module->getResourcePath('client/lang'), false, true);
155
        Requirements::javascript($module->getResourcePath("client/dist/js/bundle.js"));
156
        Requirements::css($module->getResourcePath("client/dist/styles/bundle.css"));
157
158
        CMSBatchActionHandler::register('delete', DeleteAssets::class, Folder::class);
159
    }
160
161
    public function getClientConfig()
162
    {
163
        $baseLink = $this->Link();
164
        return array_merge(parent::getClientConfig(), [
165
            'reactRouter' => true,
166
            'createFileEndpoint' => [
167
                'url' => Controller::join_links($baseLink, 'api/createFile'),
168
                'method' => 'post',
169
                'payloadFormat' => 'urlencoded',
170
            ],
171
            'uploadFileEndpoint' => [
172
                'url' => Controller::join_links($baseLink, 'api/uploadFile'),
173
                'method' => 'post',
174
                'payloadFormat' => 'urlencoded',
175
            ],
176
            'historyEndpoint' => [
177
                'url' => Controller::join_links($baseLink, 'api/history'),
178
                'method' => 'get',
179
                'responseFormat' => 'json',
180
            ],
181
            'limit' => $this->config()->page_length,
182
            'form' => [
183
                'fileEditForm' => [
184
                    'schemaUrl' => $this->Link('schema/fileEditForm')
185
                ],
186
                'fileInsertForm' => [
187
                    'schemaUrl' => $this->Link('schema/fileInsertForm')
188
                ],
189
                'fileUpdateForm' => [
190
                    'schemaUrl' => $this->Link('schema/fileUpdateForm')
191
                ],
192
                'remoteEditForm' => [
193
                    'schemaUrl' => LeftAndMain::singleton()
194
                        ->Link('Modals/remoteEditFormSchema'),
195
                ],
196
                'remoteCreateForm' => [
197
                    'schemaUrl' => LeftAndMain::singleton()
198
                        ->Link('methodSchema/Modals/remoteCreateForm')
199
                ],
200
                'fileSelectForm' => [
201
                    'schemaUrl' => $this->Link('schema/fileSelectForm')
202
                ],
203
                'addToCampaignForm' => [
204
                    'schemaUrl' => $this->Link('schema/addToCampaignForm')
205
                ],
206
                'fileHistoryForm' => [
207
                    'schemaUrl' => $this->Link('schema/fileHistoryForm')
208
                ],
209
                'fileSearchForm' => [
210
                    'schemaUrl' => $this->Link('schema/fileSearchForm')
211
                ],
212
                'folderCreateForm' => [
213
                    'schemaUrl' => $this->Link('schema/folderCreateForm')
214
                ],
215
                'fileEditorLinkForm' => [
216
                    'schemaUrl' => $this->Link('schema/fileEditorLinkForm'),
217
                ],
218
                'fileUpdateEditorLinkForm' => [
219
                    'schemaUrl' => $this->Link('schema/fileUpdateEditorLinkForm'),
220
                ],
221
            ],
222
        ]);
223
    }
224
225
    /**
226
     * Creates a single file based on a form-urlencoded upload.
227
     *
228
     * @param HTTPRequest $request
229
     * @return HTTPRequest|HTTPResponse
230
     */
231
    public function apiCreateFile(HTTPRequest $request)
232
    {
233
        $data = $request->postVars();
234
235
        // When creating new files, rename on conflict
236
        $upload = $this->getUpload();
237
        $upload->setReplaceFile(false);
238
239
        // CSRF check
240
        $token = SecurityToken::inst();
241 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...
242
            return new HTTPResponse(null, 400);
243
        }
244
245
        // Check parent record
246
        /** @var Folder $parentRecord */
247
        $parentRecord = null;
248
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
249
            $parentRecord = Folder::get()->byID($data['ParentID']);
250
        }
251
        $data['Parent'] = $parentRecord;
252
253
        $tmpFile = $request->postVar('Upload');
254 View Code Duplication
        if (!$upload->validate($tmpFile)) {
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...
255
            $result = ['message' => null];
256
            $errors = $upload->getErrors();
257
            if ($message = array_shift($errors)) {
258
                $result['message'] = [
259
                    'type' => 'error',
260
                    'value' => $message,
261
                ];
262
            }
263
            return (new HTTPResponse(json_encode($result), 400))
264
                ->addHeader('Content-Type', 'application/json');
265
        }
266
267
        // TODO Allow batch uploads
268
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
269
        /** @var File $file */
270
        $file = Injector::inst()->create($fileClass);
271
272
        // check canCreate permissions
273 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...
274
            $result = ['message' => [
275
                'type' => 'error',
276
                'value' => _t(
277
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
278
                    'You do not have permission to add files'
279
                )
280
            ]];
281
            return (new HTTPResponse(json_encode($result), 403))
282
                ->addHeader('Content-Type', 'application/json');
283
        }
284
285
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
286 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...
287
            $result = ['message' => [
288
                'type' => 'error',
289
                'value' => _t(
290
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
291
                    'Failed to load file'
292
                )
293
            ]];
294
            return (new HTTPResponse(json_encode($result), 400))
295
                ->addHeader('Content-Type', 'application/json');
296
        }
297
298
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
299
        $file->write();
300
301
        $result = [$this->getObjectFromData($file)];
302
303
        // Don't discard pre-generated client side canvas thumbnail
304
        if ($result[0]['category'] === 'image') {
305
            unset($result[0]['thumbnail']);
306
        }
307
308
        return (new HTTPResponse(json_encode($result)))
309
            ->addHeader('Content-Type', 'application/json');
310
    }
311
312
    /**
313
     * Upload a new asset for a pre-existing record. Returns the asset tuple.
314
     *
315
     * Note that conflict resolution is as follows:
316
     *  - If uploading a file with the same extension, we simply keep the same filename,
317
     *    and overwrite any existing files (same name + sha = don't duplicate).
318
     *  - If uploading a new file with a different extension, then the filename will
319
     *    be replaced, and will be checked for uniqueness against other File dataobjects.
320
     *
321
     * @param HTTPRequest $request Request containing vars 'ID' of parent record ID,
322
     * and 'Name' as form filename value
323
     * @return HTTPRequest|HTTPResponse
324
     */
325
    public function apiUploadFile(HTTPRequest $request)
326
    {
327
        $data = $request->postVars();
328
329
        // When updating files, replace on conflict
330
        $upload = $this->getUpload();
331
        $upload->setReplaceFile(true);
332
333
        // CSRF check
334
        $token = SecurityToken::inst();
335 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...
336
            return new HTTPResponse(null, 400);
337
        }
338
        $tmpFile = $data['Upload'];
339
        if (empty($data['ID']) || empty($tmpFile['name']) || !array_key_exists('Name', $data)) {
340
            return new HTTPResponse('Invalid request', 400);
341
        }
342
343
        // Check parent record
344
        /** @var File $file */
345
        $file = File::get()->byID($data['ID']);
346
        if (!$file) {
347
            return new HTTPResponse('File not found', 404);
348
        }
349
        $folder = $file->ParentID ? $file->Parent()->getFilename() : '/';
350
351
        // If extension is the same, attempt to re-use existing name
352
        if (File::get_file_extension($tmpFile['name']) === File::get_file_extension($data['Name'])) {
353
            $tmpFile['name'] = $data['Name'];
354
        } else {
355
            // If we are allowing this upload to rename the underlying record,
356
            // do a uniqueness check.
357
            $renamer = $this->getNameGenerator($tmpFile['name']);
358
            foreach ($renamer as $name) {
359
                $filename = File::join_paths($folder, $name);
360
                if (!File::find($filename)) {
361
                    $tmpFile['name'] = $name;
362
                    break;
363
                }
364
            }
365
        }
366
367 View Code Duplication
        if (!$upload->validate($tmpFile)) {
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...
368
            $result = ['message' => null];
369
            $errors = $upload->getErrors();
370
            if ($message = array_shift($errors)) {
371
                $result['message'] = [
372
                    'type' => 'error',
373
                    'value' => $message,
374
                ];
375
            }
376
            return (new HTTPResponse(json_encode($result), 400))
377
                ->addHeader('Content-Type', 'application/json');
378
        }
379
380
        try {
381
            $tuple = $upload->load($tmpFile, $folder);
382
        } catch (Exception $e) {
383
            $result = [
384
                'message' => [
385
                    'type' => 'error',
386
                    'value' => $e->getMessage(),
387
                ]
388
            ];
389
            return (new HTTPResponse(json_encode($result), 400))
390
                ->addHeader('Content-Type', 'application/json');
391
        }
392
393
        if ($upload->isError()) {
394
            $result['message'] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
395
                'type' => 'error',
396
                'value' => implode(' ' . PHP_EOL, $upload->getErrors()),
397
            ];
398
            return (new HTTPResponse(json_encode($result), 400))
399
                ->addHeader('Content-Type', 'application/json');
400
        }
401
402
        $tuple['Name'] = basename($tuple['Filename']);
403
        return (new HTTPResponse(json_encode($tuple)))
404
            ->addHeader('Content-Type', 'application/json');
405
    }
406
407
    /**
408
     * Returns a JSON array for history of a given file ID. Returns a list of all the history.
409
     *
410
     * @param HTTPRequest $request
411
     * @return HTTPResponse
412
     */
413
    public function apiHistory(HTTPRequest $request)
414
    {
415
        // CSRF check not required as the GET request has no side effects.
416
        $fileId = $request->getVar('fileId');
417
418
        if (!$fileId || !is_numeric($fileId)) {
419
            return new HTTPResponse(null, 400);
420
        }
421
422
        $class = File::class;
423
        $file = DataObject::get($class)->byID($fileId);
424
425
        if (!$file) {
426
            return new HTTPResponse(null, 404);
427
        }
428
429
        if (!$file->canView()) {
430
            return new HTTPResponse(null, 403);
431
        }
432
433
        $versions = Versioned::get_all_versions($class, $fileId)
434
            ->limit($this->config()->max_history_entries)
435
            ->sort('Version', 'DESC');
436
437
        $output = array();
438
        $next = array();
439
        $prev = null;
440
441
        // swap the order so we can get the version number to compare against.
442
        // i.e version 3 needs to know version 2 is the previous version
443
        $copy = $versions->map('Version', 'Version')->toArray();
444
        foreach (array_reverse($copy) as $k => $v) {
445
            if ($prev) {
446
                $next[$v] = $prev;
447
            }
448
449
            $prev = $v;
450
        }
451
452
        $_cachedMembers = array();
453
454
        /** @var File|AssetAdminFile $version */
455
        foreach ($versions as $version) {
456
            $author = null;
457
458
            if ($version->AuthorID) {
459
                if (!isset($_cachedMembers[$version->AuthorID])) {
460
                    $_cachedMembers[$version->AuthorID] = DataObject::get(Member::class)
461
                        ->byID($version->AuthorID);
462
                }
463
464
                $author = $_cachedMembers[$version->AuthorID];
465
            }
466
467
            if ($version->canView()) {
468
                if (isset($next[$version->Version])) {
469
                    $summary = $version->humanizedChanges(
470
                        $version->Version,
471
                        $next[$version->Version]
472
                    );
473
474
                    // if no summary returned by humanizedChanges, i.e we cannot work out what changed, just show a
475
                    // generic message
476
                    if (!$summary) {
477
                        $summary = _t(__CLASS__.'.SAVEDFILE', "Saved file");
478
                    }
479
                } else {
480
                    $summary = _t(__CLASS__.'.UPLOADEDFILE', "Uploaded file");
481
                }
482
483
                $output[] = array(
484
                    'versionid' => $version->Version,
485
                    'date_ago' => $version->dbObject('LastEdited')->Ago(),
486
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
487
                    'status' => ($version->WasPublished) ? _t(__CLASS__.'.PUBLISHED', 'Published') : '',
488
                    'author' => ($author)
489
                        ? $author->Name
490
                        : _t(__CLASS__.'.UNKNOWN', "Unknown"),
491
                    'summary' => ($summary)
492
                        ? $summary
493
                        : _t(__CLASS__.'.NOSUMMARY', "No summary available")
494
                );
495
            }
496
        }
497
498
        return
499
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
500
    }
501
502
    /**
503
     * Redirects 3.x style detail links to new 4.x style routing.
504
     *
505
     * @param HTTPRequest $request
506
     */
507
    public function legacyRedirectForEditView($request)
508
    {
509
        $fileID = $request->param('FileID');
510
        /** @var File $file */
511
        $file = File::get()->byID($fileID);
512
        $link = $this->getFileEditLink($file) ?: $this->Link();
513
        $this->redirect($link);
514
    }
515
516
    /**
517
     * Given a file return the CMS link to edit it
518
     *
519
     * @param File $file
520
     * @return string
521
     */
522
    public function getFileEditLink($file)
523
    {
524
        if (!$file || !$file->isInDB()) {
525
            return null;
526
        }
527
528
        return Controller::join_links(
529
            $this->Link('show'),
530
            $file->ParentID,
531
            'edit',
532
            $file->ID
533
        );
534
    }
535
536
    /**
537
     * Get an asset renamer for the given filename.
538
     *
539
     * @param string $filename Path name
540
     * @return AssetNameGenerator
541
     */
542
    protected function getNameGenerator($filename)
543
    {
544
        return Injector::inst()
545
            ->createWithArgs(AssetNameGenerator::class, array($filename));
546
    }
547
548
    /**
549
     * @todo Implement on client
550
     *
551
     * @param bool $unlinked
552
     * @return ArrayList
553
     */
554
    public function breadcrumbs($unlinked = false)
555
    {
556
        return null;
557
    }
558
559
560
    /**
561
     * Don't include class namespace in auto-generated CSS class
562
     */
563
    public function baseCSSClasses()
564
    {
565
        return 'AssetAdmin LeftAndMain';
566
    }
567
568
    public function providePermissions()
569
    {
570
        return array(
571
            "CMS_ACCESS_AssetAdmin" => array(
572
                'name' => _t('SilverStripe\\CMS\\Controllers\\CMSMain.ACCESS', "Access to '{title}' section", array(
573
                    'title' => static::menu_title()
574
                )),
575
                'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
576
            )
577
        );
578
    }
579
580
    /**
581
     * Build a form scaffolder for this model
582
     *
583
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
584
     *
585
     * @param File $file
586
     * @return FormFactory
587
     */
588
    public function getFormFactory(File $file)
589
    {
590
        // Get service name based on file class
591
        $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...
592
        if ($file instanceof Folder) {
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Folder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
593
            $name = FolderFormFactory::class;
594
        } elseif ($file instanceof Image) {
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Image does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
595
            $name = ImageFormFactory::class;
596
        } else {
597
            $name = FileFormFactory::class;
598
        }
599
        return Injector::inst()->get($name);
600
    }
601
602
    /**
603
     * The form is used to generate a form schema,
604
     * as well as an intermediary object to process data through API endpoints.
605
     * Since it's used directly on API endpoints, it does not have any form actions.
606
     * It handles both {@link File} and {@link Folder} records.
607
     *
608
     * @param int $id
609
     * @return Form
610
     */
611
    public function getFileEditForm($id)
612
    {
613
        return $this->getAbstractFileForm($id, 'fileEditForm');
614
    }
615
616
    /**
617
     * Get file edit form
618
     *
619
     * @param HTTPRequest $request
620
     * @return Form
621
     */
622 View Code Duplication
    public function fileEditForm($request = null)
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...
623
    {
624
        // Get ID either from posted back value, or url parameter
625
        if (!$request) {
626
            $this->httpError(400);
627
            return null;
628
        }
629
        $id = $request->param('ID');
630
        if (!$id) {
631
            $this->httpError(400);
632
            return null;
633
        }
634
        return $this->getFileEditForm($id);
635
    }
636
637
    /**
638
     * The form is used to generate a form schema,
639
     * as well as an intermediary object to process data through API endpoints.
640
     * Since it's used directly on API endpoints, it does not have any form actions.
641
     * It handles both {@link File} and {@link Folder} records.
642
     *
643
     * @param int $id
644
     * @return Form
645
     */
646
    public function getFileInsertForm($id)
647
    {
648
        return $this->getAbstractFileForm($id, 'fileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT_MEDIA ]);
649
    }
650
651
    public function getFileUpdateform($id)
652
    {
653
        return $this->getAbstractFileForm($id, 'fileUpdateForm', [ 'Type' => AssetFormFactory::TYPE_UPDATE_MEDIA ]);
654
    }
655
656
    /**
657
     * Get file insert media form
658
     *
659
     * @param HTTPRequest $request
660
     * @return Form
661
     */
662 View Code Duplication
    public function fileInsertForm($request = null)
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...
663
    {
664
        // Get ID either from posted back value, or url parameter
665
        if (!$request) {
666
            $this->httpError(400);
667
            return null;
668
        }
669
        $id = $request->param('ID');
670
        if (!$id) {
671
            $this->httpError(400);
672
            return null;
673
        }
674
        return $this->getFileInsertForm($id);
675
    }
676
677 View Code Duplication
    public function fileUpdateForm($request = null)
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...
678
    {
679
        // Get ID either from posted back value, or url parameter
680
        if (!$request) {
681
            $this->httpError(400);
682
            return null;
683
        }
684
        $id = $request->param('ID');
685
        if (!$id) {
686
            $this->httpError(400);
687
            return null;
688
        }
689
        return $this->getFileUpdateform($id);
690
    }
691
692
    /**
693
     * The form used to generate a form schema, since it's used directly on API endpoints,
694
     * it does not have any form actions.
695
     *
696
     * @param $id
697
     * @return Form
698
     */
699
    public function getFileEditorLinkForm($id)
700
    {
701
        return $this->getAbstractFileForm($id, 'fileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT_LINK ]);
702
    }
703
704
    /**
705
     * Get the file insert link form
706
     *
707
     * @param HTTPRequest $request
708
     * @return Form
709
     */
710 View Code Duplication
    public function fileEditorLinkForm($request = null)
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...
711
    {
712
        // Get ID either from posted back value, or url parameter
713
        if (!$request) {
714
            $this->httpError(400);
715
            return null;
716
        }
717
        $id = $request->param('ID');
718
        if (!$id) {
719
            $this->httpError(400);
720
            return null;
721
        }
722
        return $this->getFileInsertForm($id);
723
    }
724
725
    /**
726
     * The form used to generate a form schema, since it's used directly on API endpoints,
727
     * it does not have any form actions.
728
     *
729
     * @param $id
730
     * @return Form
731
     */
732
    public function getFileUpdateEditorLinkForm($id)
733
    {
734
        return $this->getAbstractFileForm($id, 'fileInsertForm', [ 'Type' => AssetFormFactory::TYPE_UPDATE_LINK ]);
735
    }
736
737 View Code Duplication
    public function fileUpdateEditorLinkForm($request = null)
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...
738
    {
739
        // Get ID either from posted back value, or url parameter
740
        if (!$request) {
741
            $this->httpError(400);
742
            return null;
743
        }
744
        $id = $request->param('ID');
745
        if (!$id) {
746
            $this->httpError(400);
747
            return null;
748
        }
749
        return $this->getFileUpdateForm($id);
750
    }
751
752
    /**
753
     * Abstract method for generating a form for a file
754
     *
755
     * @param int $id Record ID
756
     * @param string $name Form name
757
     * @param array $context Form context
758
     * @return Form
759
     */
760
    protected function getAbstractFileForm($id, $name, $context = [])
761
    {
762
        /** @var File $file */
763
        $file = File::get()->byID($id);
764
765
        if (!$file) {
766
            $this->httpError(404);
767
            return null;
768
        }
769
770 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...
771
            $this->httpError(403, _t(
772
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
773
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
774
                '',
775
                ['ObjectTitle' => $file->i18n_singular_name()]
776
            ));
777
            return null;
778
        }
779
780
        // Pass to form factory
781
        $augmentedContext = array_merge($context, ['Record' => $file]);
782
        $scaffolder = $this->getFormFactory($file);
783
        $form = $scaffolder->getForm($this, $name, $augmentedContext);
784
785
        // Set form action handler with ID included
786
        $form->setRequestHandler(
787
            LeftAndMainFormRequestHandler::create($form, [ $id ])
788
        );
789
790
        // Configure form to respond to validation errors with form schema
791
        // if requested via react.
792
        $form->setValidationResponseCallback(function (ValidationResult $error) use ($form, $id, $name) {
793
            $schemaId = Controller::join_links($this->Link('schema'), $name, $id);
794
            return $this->getSchemaResponse($schemaId, $form, $error);
795
        });
796
797
        return $form;
798
    }
799
800
    /**
801
     * Get form for selecting a file
802
     *
803
     * @return Form
804
     */
805
    public function fileSelectForm()
806
    {
807
        // Get ID either from posted back value, or url parameter
808
        $request = $this->getRequest();
809
        $id = $request->param('ID') ?: $request->postVar('ID');
810
        return $this->getFileSelectForm($id);
811
    }
812
813
    /**
814
     * Get form for selecting a file
815
     *
816
     * @param int $id ID of the record being selected
817
     * @return Form
818
     */
819
    public function getFileSelectForm($id)
820
    {
821
        return $this->getAbstractFileForm($id, 'fileSelectForm', [ 'Type' => AssetFormFactory::TYPE_SELECT ]);
822
    }
823
824
    /**
825
     * @param array $context
826
     * @return Form
827
     * @throws InvalidArgumentException
828
     */
829
    public function getFileHistoryForm($context)
830
    {
831
        // Check context
832
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
833
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
834
        }
835
        $id = $context['RecordID'];
836
        $versionId = $context['RecordVersion'];
837
        if (!$id || !$versionId) {
838
            return $this->httpError(404);
839
        }
840
841
        /** @var File $file */
842
        $file = Versioned::get_version(File::class, $id, $versionId);
843
        if (!$file) {
844
            return $this->httpError(404);
845
        }
846
847 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...
848
            $this->httpError(403, _t(
849
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
850
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
851
                '',
852
                ['ObjectTitle' => $file->i18n_singular_name()]
853
            ));
854
            return null;
855
        }
856
857
        $effectiveContext = array_merge($context, ['Record' => $file]);
858
        /** @var FormFactory $scaffolder */
859
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
860
        $form = $scaffolder->getForm($this, 'fileHistoryForm', $effectiveContext);
861
862
        // Set form handler with ID / VersionID
863
        $form->setRequestHandler(
864
            LeftAndMainFormRequestHandler::create($form, [ $id, $versionId ])
865
        );
866
867
        // Configure form to respond to validation errors with form schema
868
        // if requested via react.
869 View Code Duplication
        $form->setValidationResponseCallback(function (ValidationResult $errors) 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...
870
            $schemaId = Controller::join_links($this->Link('schema/fileHistoryForm'), $id, $versionId);
871
            return $this->getSchemaResponse($schemaId, $form, $errors);
872
        });
873
874
        return $form;
875
    }
876
877
    /**
878
     * Gets a JSON schema representing the current edit form.
879
     *
880
     * WARNING: Experimental API.
881
     *
882
     * @param HTTPRequest $request
883
     * @return HTTPResponse
884
     */
885
    public function schema($request)
886
    {
887
        $formName = $request->param('FormName');
888
        if ($formName !== 'fileHistoryForm') {
889
            return parent::schema($request);
890
        }
891
892
        // Get schema for history form
893
        // @todo Eventually all form scaffolding will be based on context rather than record ID
894
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
895
        $itemID = $request->param('ItemID');
896
        $version = $request->param('OtherItemID');
897
        $form = $this->getFileHistoryForm([
898
            'RecordID' => $itemID,
899
            'RecordVersion' => $version,
900
        ]);
901
902
        // Respond with this schema
903
        $response = $this->getResponse();
904
        $response->addHeader('Content-Type', 'application/json');
905
        $schemaID = $this->getRequest()->getURL();
906
        return $this->getSchemaResponse($schemaID, $form);
907
    }
908
909
    /**
910
     * Get file history form
911
     *
912
     * @param HTTPRequest $request
913
     * @return Form
914
     */
915
    public function fileHistoryForm($request = null)
916
    {
917
        // Get ID either from posted back value, or url parameter
918
        if (!$request) {
919
            $this->httpError(400);
920
            return null;
921
        }
922
        $id = $request->param('ID');
923
        if (!$id) {
924
            $this->httpError(400);
925
            return null;
926
        }
927
        $versionID = $request->param('VersionID');
928
        if (!$versionID) {
929
            $this->httpError(400);
930
            return null;
931
        }
932
        $form = $this->getFileHistoryForm([
933
            'RecordID' => $id,
934
            'RecordVersion' => $versionID,
935
        ]);
936
        return $form;
937
    }
938
939
    /**
940
     * @param array $data
941
     * @param Form $form
942
     * @return HTTPResponse
943
     */
944
    public function createfolder($data, $form)
945
    {
946
        $parentID = isset($data['ParentID']) ? intval($data['ParentID']) : 0;
947
        $data['Parent'] = null;
948
        if ($parentID) {
949
            $parent = Folder::get()->byID($parentID);
950
            if (!$parent) {
951
                throw new \InvalidArgumentException(sprintf(
952
                    '%s#%s not found',
953
                    Folder::class,
954
                    $parentID
955
                ));
956
            }
957
            $data['Parent'] = $parent;
958
        }
959
960
        // Check permission
961
        if (!Folder::singleton()->canCreate(Member::currentUser(), $data)) {
962
            throw new \InvalidArgumentException(sprintf(
963
                '%s create not allowed',
964
                Folder::class
965
            ));
966
        }
967
968
        $folder = Folder::create();
969
        $form->saveInto($folder);
970
        $folder->write();
971
972
        $createForm = $this->getFolderCreateForm($folder->ID);
973
974
        // Return the record data in the same response as the schema to save a postback
975
        $schemaData = ['record' => $this->getObjectFromData($folder)];
976
        $schemaId = Controller::join_links($this->Link('schema/folderCreateForm'), $folder->ID);
977
        return $this->getSchemaResponse($schemaId, $createForm, null, $schemaData);
978
    }
979
980
    /**
981
     * @param array $data
982
     * @param Form $form
983
     * @return HTTPResponse
984
     */
985
    public function save($data, $form)
986
    {
987
        return $this->saveOrPublish($data, $form, false);
988
    }
989
990
    /**
991
     * @param array $data
992
     * @param Form $form
993
     * @return HTTPResponse
994
     */
995
    public function publish($data, $form)
996
    {
997
        return $this->saveOrPublish($data, $form, true);
998
    }
999
1000
    /**
1001
     * Update thisrecord
1002
     *
1003
     * @param array $data
1004
     * @param Form $form
1005
     * @param bool $doPublish
1006
     * @return HTTPResponse
1007
     */
1008
    protected function saveOrPublish($data, $form, $doPublish = false)
1009
    {
1010 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...
1011
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
1012
                ->addHeader('Content-Type', 'application/json');
1013
        }
1014
1015
        $id = (int) $data['ID'];
1016
        /** @var File $record */
1017
        $record = DataObject::get_by_id(File::class, $id);
1018
1019
        if (!$record) {
1020
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
1021
                ->addHeader('Content-Type', 'application/json');
1022
        }
1023
1024
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
1025
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
1026
                ->addHeader('Content-Type', 'application/json');
1027
        }
1028
1029
        // check File extension
1030
        if (!empty($data['FileFilename'])) {
1031
            $extension = File::get_file_extension($data['FileFilename']);
1032
            $newClass = File::get_class_for_file_extension($extension);
1033
1034
            // if the class has changed, cast it to the proper class
1035
            if ($record->getClassName() !== $newClass) {
1036
                $record = $record->newClassInstance($newClass);
1037
1038
                // update the allowed category for the new file extension
1039
                $category = File::get_app_category($extension);
1040
                $record->File->setAllowedCategories($category);
1041
            }
1042
        }
1043
1044
        $form->saveInto($record);
1045
        $record->write();
1046
1047
        // Publish this record and owned objects
1048
        if ($doPublish) {
1049
            $record->publishRecursive();
1050
        }
1051
        // regenerate form, so that it constants/literals on the form are updated
1052
        $form = $this->getFileEditForm($record->ID);
1053
1054
        // Note: Force return of schema / state in success result
1055
        return $this->getRecordUpdatedResponse($record, $form);
0 ignored issues
show
Bug introduced by
It seems like $form defined by $this->getFileEditForm($record->ID) on line 1052 can be null; however, SilverStripe\AssetAdmin\...RecordUpdatedResponse() 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...
1056
    }
1057
1058
    public function unpublish($data, $form)
1059
    {
1060 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...
1061
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
1062
                ->addHeader('Content-Type', 'application/json');
1063
        }
1064
1065
        $id = (int) $data['ID'];
1066
        /** @var File $record */
1067
        $record = DataObject::get_by_id(File::class, $id);
1068
1069
        if (!$record) {
1070
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
1071
                ->addHeader('Content-Type', 'application/json');
1072
        }
1073
1074
        if (!$record->canUnpublish()) {
1075
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
1076
                ->addHeader('Content-Type', 'application/json');
1077
        }
1078
1079
        $record->doUnpublish();
1080
        return $this->getRecordUpdatedResponse($record, $form);
1081
    }
1082
1083
    /**
1084
     * @param File $file
1085
     *
1086
     * @return array
1087
     */
1088
    public function getObjectFromData(File $file)
1089
    {
1090
        $object = array(
1091
            'id' => $file->ID,
1092
            'created' => $file->Created,
1093
            'lastUpdated' => $file->LastEdited,
1094
            'owner' => null,
1095
            'parent' => null,
1096
            'title' => $file->Title,
1097
            'exists' => $file->exists(), // Broken file check
1098
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Folder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1099
            'category' => $file instanceof Folder ? 'folder' : $file->appCategory(),
0 ignored issues
show
Bug introduced by
The class SilverStripe\Assets\Folder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1100
            'name' => $file->Name,
1101
            'filename' => $file->Filename,
1102
            'extension' => $file->Extension,
1103
            'size' => $file->AbsoluteSize,
1104
            'url' => $file->AbsoluteURL,
1105
            'published' => $file->isPublished(),
1106
            'modified' => $file->isModifiedOnDraft(),
1107
            'draft' => $file->isOnDraftOnly(),
1108
            'canEdit' => $file->canEdit(),
1109
            'canDelete' => $file->canArchive(),
1110
        );
1111
1112
        /** @var Member $owner */
1113
        $owner = $file->Owner();
1114
1115
        if ($owner) {
1116
            $object['owner'] = array(
1117
                'id' => $owner->ID,
1118
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1119
            );
1120
        }
1121
1122
        /** @var Folder $parent */
1123
        $parent = $file->Parent();
1124
1125
        if ($parent) {
1126
            $object['parent'] = array(
1127
                'id' => $parent->ID,
1128
                'title' => $parent->Title,
1129
                'filename' => $parent->Filename,
1130
            );
1131
        }
1132
1133
        /** @var File $file */
1134
        if ($file->getIsImage()) {
1135
            // Small thumbnail
1136
            $smallWidth = UploadField::config()->uninherited('thumbnail_width');
1137
            $smallHeight = UploadField::config()->uninherited('thumbnail_height');
1138
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1139
            if ($smallThumbnail && $smallThumbnail->exists()) {
1140
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1141
            }
1142
1143
            // Large thumbnail
1144
            $width = $this->config()->get('thumbnail_width');
1145
            $height = $this->config()->get('thumbnail_height');
1146
            $thumbnail = $file->FitMax($width, $height);
1147
            if ($thumbnail && $thumbnail->exists()) {
1148
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1149
            }
1150
            $object['width'] = $file->Width;
1151
            $object['height'] = $file->Height;
1152
        } else {
1153
            $object['thumbnail'] = $file->PreviewLink();
1154
        }
1155
1156
        return $object;
1157
    }
1158
1159
    /**
1160
     * Action handler for adding pages to a campaign
1161
     *
1162
     * @param array $data
1163
     * @param Form $form
1164
     * @return DBHTMLText|HTTPResponse
1165
     */
1166
    public function addtocampaign($data, $form)
1167
    {
1168
        $id = $data['ID'];
1169
        $record = File::get()->byID($id);
1170
1171
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1172
        $results = $handler->addToCampaign($record, $data['Campaign']);
1173
        if (!isset($results)) {
1174
            return null;
1175
        }
1176
1177
        // Send extra "message" data with schema response
1178
        $extraData = ['message' => $results];
1179
        $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1180
        return $this->getSchemaResponse($schemaId, $form, null, $extraData);
1181
    }
1182
1183
    /**
1184
     * Url handler for add to campaign form
1185
     *
1186
     * @param HTTPRequest $request
1187
     * @return Form
1188
     */
1189
    public function addToCampaignForm($request)
1190
    {
1191
        // Get ID either from posted back value, or url parameter
1192
        $id = $request->param('ID') ?: $request->postVar('ID');
1193
        return $this->getAddToCampaignForm($id);
1194
    }
1195
1196
    /**
1197
     * @param int $id
1198
     * @return Form
1199
     */
1200
    public function getAddToCampaignForm($id)
1201
    {
1202
        // Get record-specific fields
1203
        $record = File::get()->byID($id);
1204
1205
        if (!$record) {
1206
            $this->httpError(404, _t(
1207
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1208
                'That {Type} couldn\'t be found',
1209
                '',
1210
                ['Type' => File::singleton()->i18n_singular_name()]
1211
            ));
1212
            return null;
1213
        }
1214 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...
1215
            $this->httpError(403, _t(
1216
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1217
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1218
                '',
1219
                ['ObjectTitle' => $record->i18n_singular_name()]
1220
            ));
1221
            return null;
1222
        }
1223
1224
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1225
        $form = $handler->Form($record);
1226
1227 View Code Duplication
        $form->setValidationResponseCallback(function (ValidationResult $errors) 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...
1228
            $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1229
            return $this->getSchemaResponse($schemaId, $form, $errors);
1230
        });
1231
1232
        return $form;
1233
    }
1234
1235
    /**
1236
     * @return Upload
1237
     */
1238
    protected function getUpload()
1239
    {
1240
        $upload = Upload::create();
1241
        $upload->getValidator()->setAllowedExtensions(
1242
            // filter out '' since this would be a regex problem on JS end
1243
            array_filter(File::config()->uninherited('allowed_extensions'))
1244
        );
1245
1246
        return $upload;
1247
    }
1248
1249
    /**
1250
     * Get response for successfully updated record
1251
     *
1252
     * @param File $record
1253
     * @param Form $form
1254
     * @return HTTPResponse
1255
     */
1256
    protected function getRecordUpdatedResponse($record, $form)
1257
    {
1258
        // Return the record data in the same response as the schema to save a postback
1259
        $schemaData = ['record' => $this->getObjectFromData($record)];
1260
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->ID);
1261
        return $this->getSchemaResponse($schemaId, $form, null, $schemaData);
1262
    }
1263
1264
    /**
1265
     * @param HTTPRequest $request
1266
     * @return Form
1267
     */
1268
    public function folderCreateForm($request = null)
1269
    {
1270
        // Get ID either from posted back value, or url parameter
1271
        if (!$request) {
1272
            $this->httpError(400);
1273
            return null;
1274
        }
1275
        $id = $request->param('ParentID');
1276
        // Fail on null ID (but not parent)
1277
        if (!isset($id)) {
1278
            $this->httpError(400);
1279
            return null;
1280
        }
1281
        return $this->getFolderCreateForm($id);
1282
    }
1283
1284
    /**
1285
     * Returns the form to be used for creating a new folder
1286
     * @param $parentId
1287
     * @return Form
1288
     */
1289
    public function getFolderCreateForm($parentId = 0)
1290
    {
1291
        /** @var FolderCreateFormFactory $factory */
1292
        $factory = Injector::inst()->get(FolderCreateFormFactory::class);
1293
        $form = $factory->getForm($this, 'folderCreateForm', [ 'ParentID' => $parentId ]);
1294
1295
        // Set form action handler with ParentID included
1296
        $form->setRequestHandler(
1297
            LeftAndMainFormRequestHandler::create($form, [ $parentId ])
1298
        );
1299
1300
        return $form;
1301
    }
1302
1303
    /**
1304
     * Scaffold a search form.
1305
     * Note: This form does not submit to itself, but rather uses the apiReadFolder endpoint
1306
     * (to be replaced with graphql)
1307
     *
1308
     * @return Form
1309
     */
1310
    public function fileSearchForm()
1311
    {
1312
        $scaffolder = FileSearchFormFactory::singleton();
1313
        return $scaffolder->getForm($this, 'fileSearchForm', []);
1314
    }
1315
1316
    /**
1317
     * Allow search form to be accessible to schema
1318
     *
1319
     * @return Form
1320
     */
1321
    public function getFileSearchform()
1322
    {
1323
        return $this->fileSearchForm();
1324
    }
1325
}
1326