Completed
Push — master ( a2ac0c...23423a )
by Damian
16s
created

AssetAdmin::getFileEditorLinkForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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