Completed
Push — master ( 23423a...b8da20 )
by Damian
14s
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\Security;
39
use SilverStripe\Security\SecurityToken;
40
use SilverStripe\View\Requirements;
41
use SilverStripe\Versioned\Versioned;
42
use Exception;
43
44
/**
45
 * AssetAdmin is the 'file store' section of the CMS.
46
 * It provides an interface for manipulating the File and Folder objects in the system.
47
 */
48
class AssetAdmin extends LeftAndMain implements PermissionProvider
49
{
50
    private static $url_segment = 'assets';
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_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...
51
52
    private static $url_rule = '/$Action/$ID';
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_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...
53
54
    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...
55
56
    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...
57
58
    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...
59
60
    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...
61
        // Legacy redirect for SS3-style detail view
62
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
63
        // Pass all URLs to the index, for React to unpack
64
        'show/$FolderID/edit/$FileID' => 'index',
65
        // API access points with structured data
66
        'POST api/createFile' => 'apiCreateFile',
67
        'POST api/uploadFile' => 'apiUploadFile',
68
        'GET api/history' => 'apiHistory',
69
        'fileEditForm/$ID' => 'fileEditForm',
70
        'fileInsertForm/$ID' => 'fileInsertForm',
71
        'fileEditorLinkForm/$ID' => 'fileEditorLinkForm',
72
        'fileHistoryForm/$ID/$VersionID' => 'fileHistoryForm',
73
        'folderCreateForm/$ParentID' => 'folderCreateForm',
74
    ];
75
76
    /**
77
     * Amount of results showing on a single page.
78
     *
79
     * @config
80
     * @var int
81
     */
82
    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...
83
84
    /**
85
     * @config
86
     * @see Upload->allowedMaxFileSize
87
     * @var int
88
     */
89
    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...
90
91
    /**
92
     * @config
93
     *
94
     * @var int
95
     */
96
    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...
97
98
    /**
99
     * @var array
100
     */
101
    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...
102
        'legacyRedirectForEditView',
103
        'apiCreateFile',
104
        'apiUploadFile',
105
        'apiHistory',
106
        'folderCreateForm',
107
        'fileEditForm',
108
        'fileHistoryForm',
109
        'addToCampaignForm',
110
        'fileInsertForm',
111
        'fileEditorLinkForm',
112
        'schema',
113
        'fileSelectForm',
114
        'fileSearchForm',
115
    );
116
117
    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...
118
119
    /**
120
     * Retina thumbnail image (native size: 176)
121
     *
122
     * @config
123
     * @var int
124
     */
125
    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...
126
127
    /**
128
     * Retina thumbnail height (native size: 132)
129
     *
130
     * @config
131
     * @var int
132
     */
133
    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...
134
135
    /**
136
     * Safely limit max inline thumbnail size to 200kb
137
     *
138
     * @config
139
     * @var int
140
     */
141
    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...
142
143
    /**
144
     * Set up the controller
145
     */
146
    public function init()
147
    {
148
        parent::init();
149
150
        $module = ModuleLoader::getModule('silverstripe/asset-admin');
151
        Requirements::add_i18n_javascript($module->getResourcePath('client/lang'), false, true);
152
        Requirements::javascript($module->getResourcePath("client/dist/js/bundle.js"));
153
        Requirements::css($module->getResourcePath("client/dist/styles/bundle.css"));
154
155
        CMSBatchActionHandler::register('delete', DeleteAssets::class, Folder::class);
156
    }
157
158
    public function getClientConfig()
159
    {
160
        $baseLink = $this->Link();
161
        return array_merge(parent::getClientConfig(), [
162
            'reactRouter' => true,
163
            'createFileEndpoint' => [
164
                'url' => Controller::join_links($baseLink, 'api/createFile'),
165
                'method' => 'post',
166
                'payloadFormat' => 'urlencoded',
167
            ],
168
            'uploadFileEndpoint' => [
169
                'url' => Controller::join_links($baseLink, 'api/uploadFile'),
170
                'method' => 'post',
171
                'payloadFormat' => 'urlencoded',
172
            ],
173
            'historyEndpoint' => [
174
                'url' => Controller::join_links($baseLink, 'api/history'),
175
                'method' => 'get',
176
                'responseFormat' => 'json',
177
            ],
178
            'limit' => $this->config()->page_length,
179
            'form' => [
180
                'fileEditForm' => [
181
                    'schemaUrl' => $this->Link('schema/fileEditForm')
182
                ],
183
                'fileInsertForm' => [
184
                    'schemaUrl' => $this->Link('schema/fileInsertForm')
185
                ],
186
                'remoteEditForm' => [
187
                    'schemaUrl' => LeftAndMain::singleton()
188
                        ->Link('Modals/remoteEditFormSchema'),
189
                ],
190
                'remoteCreateForm' => [
191
                    'schemaUrl' => LeftAndMain::singleton()
192
                        ->Link('methodSchema/Modals/remoteCreateForm')
193
                ],
194
                'fileSelectForm' => [
195
                    'schemaUrl' => $this->Link('schema/fileSelectForm')
196
                ],
197
                'addToCampaignForm' => [
198
                    'schemaUrl' => $this->Link('schema/addToCampaignForm')
199
                ],
200
                'fileHistoryForm' => [
201
                    'schemaUrl' => $this->Link('schema/fileHistoryForm')
202
                ],
203
                'fileSearchForm' => [
204
                    'schemaUrl' => $this->Link('schema/fileSearchForm')
205
                ],
206
                'folderCreateForm' => [
207
                    'schemaUrl' => $this->Link('schema/folderCreateForm')
208
                ],
209
                'fileEditorLinkForm' => [
210
                    'schemaUrl' => $this->Link('schema/fileEditorLinkForm'),
211
                ],
212
            ],
213
        ]);
214
    }
215
216
    /**
217
     * Creates a single file based on a form-urlencoded upload.
218
     *
219
     * @param HTTPRequest $request
220
     * @return HTTPRequest|HTTPResponse
221
     */
222
    public function apiCreateFile(HTTPRequest $request)
223
    {
224
        $data = $request->postVars();
225
226
        // When creating new files, rename on conflict
227
        $upload = $this->getUpload();
228
        $upload->setReplaceFile(false);
229
230
        // CSRF check
231
        $token = SecurityToken::inst();
232 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...
233
            return new HTTPResponse(null, 400);
234
        }
235
236
        // Check parent record
237
        /** @var Folder $parentRecord */
238
        $parentRecord = null;
239
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
240
            $parentRecord = Folder::get()->byID($data['ParentID']);
241
        }
242
        $data['Parent'] = $parentRecord;
243
244
        $tmpFile = $request->postVar('Upload');
245 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...
246
            $result = ['message' => null];
247
            $errors = $upload->getErrors();
248
            if ($message = array_shift($errors)) {
249
                $result['message'] = [
250
                    'type' => 'error',
251
                    'value' => $message,
252
                ];
253
            }
254
            return (new HTTPResponse(json_encode($result), 400))
255
                ->addHeader('Content-Type', 'application/json');
256
        }
257
258
        // TODO Allow batch uploads
259
        $fileClass = File::get_class_for_file_extension(File::get_file_extension($tmpFile['name']));
260
        /** @var File $file */
261
        $file = Injector::inst()->create($fileClass);
262
263
        // check canCreate permissions
264 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...
265
            $result = ['message' => [
266
                'type' => 'error',
267
                'value' => _t(
268
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.CreatePermissionDenied',
269
                    'You do not have permission to add files'
270
                )
271
            ]];
272
            return (new HTTPResponse(json_encode($result), 403))
273
                ->addHeader('Content-Type', 'application/json');
274
        }
275
276
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
277 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...
278
            $result = ['message' => [
279
                'type' => 'error',
280
                'value' => _t(
281
                    'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.LoadIntoFileFailed',
282
                    'Failed to load file'
283
                )
284
            ]];
285
            return (new HTTPResponse(json_encode($result), 400))
286
                ->addHeader('Content-Type', 'application/json');
287
        }
288
289
        $file->ParentID = $parentRecord ? $parentRecord->ID : 0;
290
        $file->write();
291
292
        $result = [$this->getObjectFromData($file)];
293
294
        // Don't discard pre-generated client side canvas thumbnail
295
        if ($result[0]['category'] === 'image') {
296
            unset($result[0]['thumbnail']);
297
        }
298
299
        return (new HTTPResponse(json_encode($result)))
300
            ->addHeader('Content-Type', 'application/json');
301
    }
302
303
    /**
304
     * Upload a new asset for a pre-existing record. Returns the asset tuple.
305
     *
306
     * Note that conflict resolution is as follows:
307
     *  - If uploading a file with the same extension, we simply keep the same filename,
308
     *    and overwrite any existing files (same name + sha = don't duplicate).
309
     *  - If uploading a new file with a different extension, then the filename will
310
     *    be replaced, and will be checked for uniqueness against other File dataobjects.
311
     *
312
     * @param HTTPRequest $request Request containing vars 'ID' of parent record ID,
313
     * and 'Name' as form filename value
314
     * @return HTTPRequest|HTTPResponse
315
     */
316
    public function apiUploadFile(HTTPRequest $request)
317
    {
318
        $data = $request->postVars();
319
320
        // When updating files, replace on conflict
321
        $upload = $this->getUpload();
322
        $upload->setReplaceFile(true);
323
324
        // CSRF check
325
        $token = SecurityToken::inst();
326 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...
327
            return new HTTPResponse(null, 400);
328
        }
329
        $tmpFile = $data['Upload'];
330
        if (empty($data['ID']) || empty($tmpFile['name']) || !array_key_exists('Name', $data)) {
331
            return new HTTPResponse('Invalid request', 400);
332
        }
333
334
        // Check parent record
335
        /** @var File $file */
336
        $file = File::get()->byID($data['ID']);
337
        if (!$file) {
338
            return new HTTPResponse('File not found', 404);
339
        }
340
        $folder = $file->ParentID ? $file->Parent()->getFilename() : '/';
341
342
        // If extension is the same, attempt to re-use existing name
343
        if (File::get_file_extension($tmpFile['name']) === File::get_file_extension($data['Name'])) {
344
            $tmpFile['name'] = $data['Name'];
345
        } else {
346
            // If we are allowing this upload to rename the underlying record,
347
            // do a uniqueness check.
348
            $renamer = $this->getNameGenerator($tmpFile['name']);
349
            foreach ($renamer as $name) {
350
                $filename = File::join_paths($folder, $name);
351
                if (!File::find($filename)) {
352
                    $tmpFile['name'] = $name;
353
                    break;
354
                }
355
            }
356
        }
357
358 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...
359
            $result = ['message' => null];
360
            $errors = $upload->getErrors();
361
            if ($message = array_shift($errors)) {
362
                $result['message'] = [
363
                    'type' => 'error',
364
                    'value' => $message,
365
                ];
366
            }
367
            return (new HTTPResponse(json_encode($result), 400))
368
                ->addHeader('Content-Type', 'application/json');
369
        }
370
371
        try {
372
            $tuple = $upload->load($tmpFile, $folder);
373
        } catch (Exception $e) {
374
            $result = [
375
                'message' => [
376
                    'type' => 'error',
377
                    'value' => $e->getMessage(),
378
                ]
379
            ];
380
            return (new HTTPResponse(json_encode($result), 400))
381
                ->addHeader('Content-Type', 'application/json');
382
        }
383
384
        if ($upload->isError()) {
385
            $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...
386
                'type' => 'error',
387
                'value' => implode(' ' . PHP_EOL, $upload->getErrors()),
388
            ];
389
            return (new HTTPResponse(json_encode($result), 400))
390
                ->addHeader('Content-Type', 'application/json');
391
        }
392
393
        $tuple['Name'] = basename($tuple['Filename']);
394
        return (new HTTPResponse(json_encode($tuple)))
395
            ->addHeader('Content-Type', 'application/json');
396
    }
397
398
    /**
399
     * Returns a JSON array for history of a given file ID. Returns a list of all the history.
400
     *
401
     * @param HTTPRequest $request
402
     * @return HTTPResponse
403
     */
404
    public function apiHistory(HTTPRequest $request)
405
    {
406
        // CSRF check not required as the GET request has no side effects.
407
        $fileId = $request->getVar('fileId');
408
409
        if (!$fileId || !is_numeric($fileId)) {
410
            return new HTTPResponse(null, 400);
411
        }
412
413
        $class = File::class;
414
        $file = DataObject::get($class)->byID($fileId);
415
416
        if (!$file) {
417
            return new HTTPResponse(null, 404);
418
        }
419
420
        if (!$file->canView()) {
421
            return new HTTPResponse(null, 403);
422
        }
423
424
        $versions = Versioned::get_all_versions($class, $fileId)
425
            ->limit($this->config()->max_history_entries)
426
            ->sort('Version', 'DESC');
427
428
        $output = array();
429
        $next = array();
430
        $prev = null;
431
432
        // swap the order so we can get the version number to compare against.
433
        // i.e version 3 needs to know version 2 is the previous version
434
        $copy = $versions->map('Version', 'Version')->toArray();
435
        foreach (array_reverse($copy) as $k => $v) {
436
            if ($prev) {
437
                $next[$v] = $prev;
438
            }
439
440
            $prev = $v;
441
        }
442
443
        $_cachedMembers = array();
444
445
        /** @var File|AssetAdminFile $version */
446
        foreach ($versions as $version) {
447
            $author = null;
448
449
            if ($version->AuthorID) {
450
                if (!isset($_cachedMembers[$version->AuthorID])) {
451
                    $_cachedMembers[$version->AuthorID] = DataObject::get(Member::class)
452
                        ->byID($version->AuthorID);
453
                }
454
455
                $author = $_cachedMembers[$version->AuthorID];
456
            }
457
458
            if ($version->canView()) {
459
                if (isset($next[$version->Version])) {
460
                    $summary = $version->humanizedChanges(
461
                        $version->Version,
462
                        $next[$version->Version]
463
                    );
464
465
                    // if no summary returned by humanizedChanges, i.e we cannot work out what changed, just show a
466
                    // generic message
467
                    if (!$summary) {
468
                        $summary = _t(__CLASS__.'.SAVEDFILE', "Saved file");
469
                    }
470
                } else {
471
                    $summary = _t(__CLASS__.'.UPLOADEDFILE', "Uploaded file");
472
                }
473
474
                $output[] = array(
475
                    'versionid' => $version->Version,
476
                    'date_ago' => $version->dbObject('LastEdited')->Ago(),
477
                    'date_formatted' => $version->dbObject('LastEdited')->Nice(),
478
                    'status' => ($version->WasPublished) ? _t(__CLASS__.'.PUBLISHED', 'Published') : '',
479
                    'author' => ($author)
480
                        ? $author->Name
481
                        : _t(__CLASS__.'.UNKNOWN', "Unknown"),
482
                    'summary' => ($summary)
483
                        ? $summary
484
                        : _t(__CLASS__.'.NOSUMMARY', "No summary available")
485
                );
486
            }
487
        }
488
489
        return
490
            (new HTTPResponse(json_encode($output)))->addHeader('Content-Type', 'application/json');
491
    }
492
493
    /**
494
     * Redirects 3.x style detail links to new 4.x style routing.
495
     *
496
     * @param HTTPRequest $request
497
     */
498
    public function legacyRedirectForEditView($request)
499
    {
500
        $fileID = $request->param('FileID');
501
        /** @var File $file */
502
        $file = File::get()->byID($fileID);
503
        $link = $this->getFileEditLink($file) ?: $this->Link();
504
        $this->redirect($link);
505
    }
506
507
    /**
508
     * Given a file return the CMS link to edit it
509
     *
510
     * @param File $file
511
     * @return string
512
     */
513
    public function getFileEditLink($file)
514
    {
515
        if (!$file || !$file->isInDB()) {
516
            return null;
517
        }
518
519
        return Controller::join_links(
520
            $this->Link('show'),
521
            $file->ParentID,
522
            'edit',
523
            $file->ID
524
        );
525
    }
526
527
    /**
528
     * Get an asset renamer for the given filename.
529
     *
530
     * @param string $filename Path name
531
     * @return AssetNameGenerator
532
     */
533
    protected function getNameGenerator($filename)
534
    {
535
        return Injector::inst()
536
            ->createWithArgs(AssetNameGenerator::class, array($filename));
537
    }
538
539
    /**
540
     * @todo Implement on client
541
     *
542
     * @param bool $unlinked
543
     * @return ArrayList
544
     */
545
    public function breadcrumbs($unlinked = false)
546
    {
547
        return null;
548
    }
549
550
551
    /**
552
     * Don't include class namespace in auto-generated CSS class
553
     */
554
    public function baseCSSClasses()
555
    {
556
        return 'AssetAdmin LeftAndMain';
557
    }
558
559
    public function providePermissions()
560
    {
561
        return array(
562
            "CMS_ACCESS_AssetAdmin" => array(
563
                'name' => _t('SilverStripe\\CMS\\Controllers\\CMSMain.ACCESS', "Access to '{title}' section", array(
564
                    'title' => static::menu_title()
565
                )),
566
                'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
567
            )
568
        );
569
    }
570
571
    /**
572
     * Build a form scaffolder for this model
573
     *
574
     * NOTE: Volatile api. May be moved to {@see LeftAndMain}
575
     *
576
     * @param File $file
577
     * @return FormFactory
578
     */
579
    public function getFormFactory(File $file)
580
    {
581
        // Get service name based on file class
582
        $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...
583
        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...
584
            $name = FolderFormFactory::class;
585
        } 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...
586
            $name = ImageFormFactory::class;
587
        } else {
588
            $name = FileFormFactory::class;
589
        }
590
        return Injector::inst()->get($name);
591
    }
592
593
    /**
594
     * The form is used to generate a form schema,
595
     * as well as an intermediary object to process data through API endpoints.
596
     * Since it's used directly on API endpoints, it does not have any form actions.
597
     * It handles both {@link File} and {@link Folder} records.
598
     *
599
     * @param int $id
600
     * @return Form
601
     */
602
    public function getFileEditForm($id)
603
    {
604
        return $this->getAbstractFileForm($id, 'fileEditForm');
605
    }
606
607
    /**
608
     * Get file edit form
609
     *
610
     * @param HTTPRequest $request
611
     * @return Form
612
     */
613 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...
614
    {
615
        // Get ID either from posted back value, or url parameter
616
        if (!$request) {
617
            $this->httpError(400);
618
            return null;
619
        }
620
        $id = $request->param('ID');
621
        if (!$id) {
622
            $this->httpError(400);
623
            return null;
624
        }
625
        return $this->getFileEditForm($id);
626
    }
627
628
    /**
629
     * The form is used to generate a form schema,
630
     * as well as an intermediary object to process data through API endpoints.
631
     * Since it's used directly on API endpoints, it does not have any form actions.
632
     * It handles both {@link File} and {@link Folder} records.
633
     *
634
     * @param int $id
635
     * @return Form
636
     */
637
    public function getFileInsertForm($id)
638
    {
639
        return $this->getAbstractFileForm($id, 'fileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT_MEDIA ]);
640
    }
641
642
    /**
643
     * Get file insert media form
644
     *
645
     * @param HTTPRequest $request
646
     * @return Form
647
     */
648 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...
649
    {
650
        // Get ID either from posted back value, or url parameter
651
        if (!$request) {
652
            $this->httpError(400);
653
            return null;
654
        }
655
        $id = $request->param('ID');
656
        if (!$id) {
657
            $this->httpError(400);
658
            return null;
659
        }
660
        return $this->getFileInsertForm($id);
661
    }
662
    
663
    /**
664
     * The form used to generate a form schema, since it's used directly on API endpoints,
665
     * it does not have any form actions.
666
     *
667
     * @param $id
668
     * @return Form
669
     */
670
    public function getFileEditorLinkForm($id)
671
    {
672
        return $this->getAbstractFileForm($id, 'fileInsertForm', [ 'Type' => AssetFormFactory::TYPE_INSERT_LINK ]);
673
    }
674
    
675
    /**
676
     * Get the file insert link form
677
     *
678
     * @param HTTPRequest $request
679
     * @return Form
680
     */
681 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...
682
    {
683
        // Get ID either from posted back value, or url parameter
684
        if (!$request) {
685
            $this->httpError(400);
686
            return null;
687
        }
688
        $id = $request->param('ID');
689
        if (!$id) {
690
            $this->httpError(400);
691
            return null;
692
        }
693
        return $this->getFileInsertForm($id);
694
    }
695
    
696
    /**
697
     * Abstract method for generating a form for a file
698
     *
699
     * @param int $id Record ID
700
     * @param string $name Form name
701
     * @param array $context Form context
702
     * @return Form
703
     */
704
    protected function getAbstractFileForm($id, $name, $context = [])
705
    {
706
        /** @var File $file */
707
        $file = File::get()->byID($id);
708
709
        if (!$file) {
710
            $this->httpError(404);
711
            return null;
712
        }
713
714 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...
715
            $this->httpError(403, _t(
716
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
717
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
718
                '',
719
                ['ObjectTitle' => $file->i18n_singular_name()]
720
            ));
721
            return null;
722
        }
723
724
        // Pass to form factory
725
        $augmentedContext = array_merge($context, ['Record' => $file]);
726
        $scaffolder = $this->getFormFactory($file);
727
        $form = $scaffolder->getForm($this, $name, $augmentedContext);
728
729
        // Set form action handler with ID included
730
        $form->setRequestHandler(
731
            LeftAndMainFormRequestHandler::create($form, [ $id ])
732
        );
733
734
        // Configure form to respond to validation errors with form schema
735
        // if requested via react.
736
        $form->setValidationResponseCallback(function (ValidationResult $error) use ($form, $id, $name) {
737
            $schemaId = Controller::join_links($this->Link('schema'), $name, $id);
738
            return $this->getSchemaResponse($schemaId, $form, $error);
739
        });
740
741
        return $form;
742
    }
743
744
    /**
745
     * Get form for selecting a file
746
     *
747
     * @return Form
748
     */
749
    public function fileSelectForm()
750
    {
751
        // Get ID either from posted back value, or url parameter
752
        $request = $this->getRequest();
753
        $id = $request->param('ID') ?: $request->postVar('ID');
754
        return $this->getFileSelectForm($id);
755
    }
756
757
    /**
758
     * Get form for selecting a file
759
     *
760
     * @param int $id ID of the record being selected
761
     * @return Form
762
     */
763
    public function getFileSelectForm($id)
764
    {
765
        return $this->getAbstractFileForm($id, 'fileSelectForm', [ 'Type' => AssetFormFactory::TYPE_SELECT ]);
766
    }
767
768
    /**
769
     * @param array $context
770
     * @return Form
771
     * @throws InvalidArgumentException
772
     */
773
    public function getFileHistoryForm($context)
774
    {
775
        // Check context
776
        if (!isset($context['RecordID']) || !isset($context['RecordVersion'])) {
777
            throw new InvalidArgumentException("Missing RecordID / RecordVersion for this form");
778
        }
779
        $id = $context['RecordID'];
780
        $versionId = $context['RecordVersion'];
781
        if (!$id || !$versionId) {
782
            return $this->httpError(404);
783
        }
784
785
        /** @var File $file */
786
        $file = Versioned::get_version(File::class, $id, $versionId);
787
        if (!$file) {
788
            return $this->httpError(404);
789
        }
790
791 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...
792
            $this->httpError(403, _t(
793
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
794
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
795
                '',
796
                ['ObjectTitle' => $file->i18n_singular_name()]
797
            ));
798
            return null;
799
        }
800
801
        $effectiveContext = array_merge($context, ['Record' => $file]);
802
        /** @var FormFactory $scaffolder */
803
        $scaffolder = Injector::inst()->get(FileHistoryFormFactory::class);
804
        $form = $scaffolder->getForm($this, 'fileHistoryForm', $effectiveContext);
805
806
        // Set form handler with ID / VersionID
807
        $form->setRequestHandler(
808
            LeftAndMainFormRequestHandler::create($form, [ $id, $versionId ])
809
        );
810
811
        // Configure form to respond to validation errors with form schema
812
        // if requested via react.
813 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...
814
            $schemaId = Controller::join_links($this->Link('schema/fileHistoryForm'), $id, $versionId);
815
            return $this->getSchemaResponse($schemaId, $form, $errors);
816
        });
817
818
        return $form;
819
    }
820
821
    /**
822
     * Gets a JSON schema representing the current edit form.
823
     *
824
     * WARNING: Experimental API.
825
     *
826
     * @param HTTPRequest $request
827
     * @return HTTPResponse
828
     */
829
    public function schema($request)
830
    {
831
        $formName = $request->param('FormName');
832
        if ($formName !== 'fileHistoryForm') {
833
            return parent::schema($request);
834
        }
835
836
        // Get schema for history form
837
        // @todo Eventually all form scaffolding will be based on context rather than record ID
838
        // See https://github.com/silverstripe/silverstripe-framework/issues/6362
839
        $itemID = $request->param('ItemID');
840
        $version = $request->param('OtherItemID');
841
        $form = $this->getFileHistoryForm([
842
            'RecordID' => $itemID,
843
            'RecordVersion' => $version,
844
        ]);
845
846
        // Respond with this schema
847
        $response = $this->getResponse();
848
        $response->addHeader('Content-Type', 'application/json');
849
        $schemaID = $this->getRequest()->getURL();
850
        return $this->getSchemaResponse($schemaID, $form);
851
    }
852
853
    /**
854
     * Get file history form
855
     *
856
     * @param HTTPRequest $request
857
     * @return Form
858
     */
859
    public function fileHistoryForm($request = null)
860
    {
861
        // Get ID either from posted back value, or url parameter
862
        if (!$request) {
863
            $this->httpError(400);
864
            return null;
865
        }
866
        $id = $request->param('ID');
867
        if (!$id) {
868
            $this->httpError(400);
869
            return null;
870
        }
871
        $versionID = $request->param('VersionID');
872
        if (!$versionID) {
873
            $this->httpError(400);
874
            return null;
875
        }
876
        $form = $this->getFileHistoryForm([
877
            'RecordID' => $id,
878
            'RecordVersion' => $versionID,
879
        ]);
880
        return $form;
881
    }
882
883
    /**
884
     * @param array $data
885
     * @param Form $form
886
     * @return HTTPResponse
887
     */
888
    public function createfolder($data, $form)
889
    {
890
        $parentID = isset($data['ParentID']) ? intval($data['ParentID']) : 0;
891
        $data['Parent'] = null;
892
        if ($parentID) {
893
            $parent = Folder::get()->byID($parentID);
894
            if (!$parent) {
895
                throw new \InvalidArgumentException(sprintf(
896
                    '%s#%s not found',
897
                    Folder::class,
898
                    $parentID
899
                ));
900
            }
901
            $data['Parent'] = $parent;
902
        }
903
904
        // Check permission
905
        if (!Folder::singleton()->canCreate(Security::getCurrentUser(), $data)) {
0 ignored issues
show
Bug introduced by
The method getCurrentUser() does not seem to exist on object<SilverStripe\Security\Security>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
906
            throw new \InvalidArgumentException(sprintf(
907
                '%s create not allowed',
908
                Folder::class
909
            ));
910
        }
911
912
        $folder = Folder::create();
913
        $form->saveInto($folder);
914
        $folder->write();
915
916
        $createForm = $this->getFolderCreateForm($folder->ID);
917
918
        // Return the record data in the same response as the schema to save a postback
919
        $schemaData = ['record' => $this->getObjectFromData($folder)];
920
        $schemaId = Controller::join_links($this->Link('schema/folderCreateForm'), $folder->ID);
921
        return $this->getSchemaResponse($schemaId, $createForm, null, $schemaData);
922
    }
923
924
    /**
925
     * @param array $data
926
     * @param Form $form
927
     * @return HTTPResponse
928
     */
929
    public function save($data, $form)
930
    {
931
        return $this->saveOrPublish($data, $form, false);
932
    }
933
934
    /**
935
     * @param array $data
936
     * @param Form $form
937
     * @return HTTPResponse
938
     */
939
    public function publish($data, $form)
940
    {
941
        return $this->saveOrPublish($data, $form, true);
942
    }
943
944
    /**
945
     * Update thisrecord
946
     *
947
     * @param array $data
948
     * @param Form $form
949
     * @param bool $doPublish
950
     * @return HTTPResponse
951
     */
952
    protected function saveOrPublish($data, $form, $doPublish = false)
953
    {
954 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...
955
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
956
                ->addHeader('Content-Type', 'application/json');
957
        }
958
959
        $id = (int) $data['ID'];
960
        /** @var File $record */
961
        $record = DataObject::get_by_id(File::class, $id);
962
963
        if (!$record) {
964
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
965
                ->addHeader('Content-Type', 'application/json');
966
        }
967
968
        if (!$record->canEdit() || ($doPublish && !$record->canPublish())) {
969
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
970
                ->addHeader('Content-Type', 'application/json');
971
        }
972
973
        // check File extension
974
        if (!empty($data['FileFilename'])) {
975
            $extension = File::get_file_extension($data['FileFilename']);
976
            $newClass = File::get_class_for_file_extension($extension);
977
978
            // if the class has changed, cast it to the proper class
979
            if ($record->getClassName() !== $newClass) {
980
                $record = $record->newClassInstance($newClass);
981
982
                // update the allowed category for the new file extension
983
                $category = File::get_app_category($extension);
984
                $record->File->setAllowedCategories($category);
985
            }
986
        }
987
988
        $form->saveInto($record);
989
        $record->write();
990
991
        // Publish this record and owned objects
992
        if ($doPublish) {
993
            $record->publishRecursive();
994
        }
995
        // regenerate form, so that it constants/literals on the form are updated
996
        $form = $this->getFileEditForm($record->ID);
997
998
        // Note: Force return of schema / state in success result
999
        return $this->getRecordUpdatedResponse($record, $form);
0 ignored issues
show
Bug introduced by
It seems like $form defined by $this->getFileEditForm($record->ID) on line 996 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...
1000
    }
1001
1002
    public function unpublish($data, $form)
1003
    {
1004 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...
1005
            return (new HTTPResponse(json_encode(['status' => 'error']), 400))
1006
                ->addHeader('Content-Type', 'application/json');
1007
        }
1008
1009
        $id = (int) $data['ID'];
1010
        /** @var File $record */
1011
        $record = DataObject::get_by_id(File::class, $id);
1012
1013
        if (!$record) {
1014
            return (new HTTPResponse(json_encode(['status' => 'error']), 404))
1015
                ->addHeader('Content-Type', 'application/json');
1016
        }
1017
1018
        if (!$record->canUnpublish()) {
1019
            return (new HTTPResponse(json_encode(['status' => 'error']), 401))
1020
                ->addHeader('Content-Type', 'application/json');
1021
        }
1022
1023
        $record->doUnpublish();
1024
        return $this->getRecordUpdatedResponse($record, $form);
1025
    }
1026
1027
    /**
1028
     * @param File $file
1029
     *
1030
     * @return array
1031
     */
1032
    public function getObjectFromData(File $file)
1033
    {
1034
        $object = array(
1035
            'id' => $file->ID,
1036
            'created' => $file->Created,
1037
            'lastUpdated' => $file->LastEdited,
1038
            'owner' => null,
1039
            'parent' => null,
1040
            'title' => $file->Title,
1041
            'exists' => $file->exists(), // Broken file check
1042
            'type' => $file instanceof Folder ? 'folder' : $file->FileType,
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
            '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...
1044
            'name' => $file->Name,
1045
            'filename' => $file->Filename,
1046
            'extension' => $file->Extension,
1047
            'size' => $file->AbsoluteSize,
1048
            'url' => $file->AbsoluteURL,
1049
            'published' => $file->isPublished(),
1050
            'modified' => $file->isModifiedOnDraft(),
1051
            'draft' => $file->isOnDraftOnly(),
1052
            'canEdit' => $file->canEdit(),
1053
            'canDelete' => $file->canArchive(),
1054
        );
1055
1056
        /** @var Member $owner */
1057
        $owner = $file->Owner();
1058
1059
        if ($owner) {
1060
            $object['owner'] = array(
1061
                'id' => $owner->ID,
1062
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
1063
            );
1064
        }
1065
1066
        /** @var Folder $parent */
1067
        $parent = $file->Parent();
1068
1069
        if ($parent) {
1070
            $object['parent'] = array(
1071
                'id' => $parent->ID,
1072
                'title' => $parent->Title,
1073
                'filename' => $parent->Filename,
1074
            );
1075
        }
1076
1077
        /** @var File $file */
1078
        if ($file->getIsImage()) {
1079
            // Small thumbnail
1080
            $smallWidth = UploadField::config()->uninherited('thumbnail_width');
1081
            $smallHeight = UploadField::config()->uninherited('thumbnail_height');
1082
            $smallThumbnail = $file->FitMax($smallWidth, $smallHeight);
1083
            if ($smallThumbnail && $smallThumbnail->exists()) {
1084
                $object['smallThumbnail'] = $smallThumbnail->getAbsoluteURL();
1085
            }
1086
1087
            // Large thumbnail
1088
            $width = $this->config()->get('thumbnail_width');
1089
            $height = $this->config()->get('thumbnail_height');
1090
            $thumbnail = $file->FitMax($width, $height);
1091
            if ($thumbnail && $thumbnail->exists()) {
1092
                $object['thumbnail'] = $thumbnail->getAbsoluteURL();
1093
            }
1094
            $object['width'] = $file->Width;
1095
            $object['height'] = $file->Height;
1096
        } else {
1097
            $object['thumbnail'] = $file->PreviewLink();
1098
        }
1099
1100
        return $object;
1101
    }
1102
1103
    /**
1104
     * Action handler for adding pages to a campaign
1105
     *
1106
     * @param array $data
1107
     * @param Form $form
1108
     * @return DBHTMLText|HTTPResponse
1109
     */
1110
    public function addtocampaign($data, $form)
1111
    {
1112
        $id = $data['ID'];
1113
        $record = File::get()->byID($id);
1114
1115
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1116
        $results = $handler->addToCampaign($record, $data['Campaign']);
1117
        if (!isset($results)) {
1118
            return null;
1119
        }
1120
1121
        // Send extra "message" data with schema response
1122
        $extraData = ['message' => $results];
1123
        $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1124
        return $this->getSchemaResponse($schemaId, $form, null, $extraData);
1125
    }
1126
1127
    /**
1128
     * Url handler for add to campaign form
1129
     *
1130
     * @param HTTPRequest $request
1131
     * @return Form
1132
     */
1133
    public function addToCampaignForm($request)
1134
    {
1135
        // Get ID either from posted back value, or url parameter
1136
        $id = $request->param('ID') ?: $request->postVar('ID');
1137
        return $this->getAddToCampaignForm($id);
1138
    }
1139
1140
    /**
1141
     * @param int $id
1142
     * @return Form
1143
     */
1144
    public function getAddToCampaignForm($id)
1145
    {
1146
        // Get record-specific fields
1147
        $record = File::get()->byID($id);
1148
1149
        if (!$record) {
1150
            $this->httpError(404, _t(
1151
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorNotFound',
1152
                'That {Type} couldn\'t be found',
1153
                '',
1154
                ['Type' => File::singleton()->i18n_singular_name()]
1155
            ));
1156
            return null;
1157
        }
1158 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...
1159
            $this->httpError(403, _t(
1160
                'SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.ErrorItemPermissionDenied',
1161
                'You don\'t have the necessary permissions to modify {ObjectTitle}',
1162
                '',
1163
                ['ObjectTitle' => $record->i18n_singular_name()]
1164
            ));
1165
            return null;
1166
        }
1167
1168
        $handler = AddToCampaignHandler::create($this, $record, 'addToCampaignForm');
1169
        $form = $handler->Form($record);
1170
1171 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...
1172
            $schemaId = Controller::join_links($this->Link('schema/addToCampaignForm'), $id);
1173
            return $this->getSchemaResponse($schemaId, $form, $errors);
1174
        });
1175
1176
        return $form;
1177
    }
1178
1179
    /**
1180
     * @return Upload
1181
     */
1182
    protected function getUpload()
1183
    {
1184
        $upload = Upload::create();
1185
        $upload->getValidator()->setAllowedExtensions(
1186
            // filter out '' since this would be a regex problem on JS end
1187
            array_filter(File::config()->uninherited('allowed_extensions'))
1188
        );
1189
1190
        return $upload;
1191
    }
1192
1193
    /**
1194
     * Get response for successfully updated record
1195
     *
1196
     * @param File $record
1197
     * @param Form $form
1198
     * @return HTTPResponse
1199
     */
1200
    protected function getRecordUpdatedResponse($record, $form)
1201
    {
1202
        // Return the record data in the same response as the schema to save a postback
1203
        $schemaData = ['record' => $this->getObjectFromData($record)];
1204
        $schemaId = Controller::join_links($this->Link('schema/fileEditForm'), $record->ID);
1205
        return $this->getSchemaResponse($schemaId, $form, null, $schemaData);
1206
    }
1207
1208
    /**
1209
     * @param HTTPRequest $request
1210
     * @return Form
1211
     */
1212
    public function folderCreateForm($request = null)
1213
    {
1214
        // Get ID either from posted back value, or url parameter
1215
        if (!$request) {
1216
            $this->httpError(400);
1217
            return null;
1218
        }
1219
        $id = $request->param('ParentID');
1220
        // Fail on null ID (but not parent)
1221
        if (!isset($id)) {
1222
            $this->httpError(400);
1223
            return null;
1224
        }
1225
        return $this->getFolderCreateForm($id);
1226
    }
1227
1228
    /**
1229
     * Returns the form to be used for creating a new folder
1230
     * @param $parentId
1231
     * @return Form
1232
     */
1233
    public function getFolderCreateForm($parentId = 0)
1234
    {
1235
        /** @var FolderCreateFormFactory $factory */
1236
        $factory = Injector::inst()->get(FolderCreateFormFactory::class);
1237
        $form = $factory->getForm($this, 'folderCreateForm', [ 'ParentID' => $parentId ]);
1238
1239
        // Set form action handler with ParentID included
1240
        $form->setRequestHandler(
1241
            LeftAndMainFormRequestHandler::create($form, [ $parentId ])
1242
        );
1243
1244
        return $form;
1245
    }
1246
1247
    /**
1248
     * Scaffold a search form.
1249
     * Note: This form does not submit to itself, but rather uses the apiReadFolder endpoint
1250
     * (to be replaced with graphql)
1251
     *
1252
     * @return Form
1253
     */
1254
    public function fileSearchForm()
1255
    {
1256
        $scaffolder = FileSearchFormFactory::singleton();
1257
        return $scaffolder->getForm($this, 'fileSearchForm', []);
1258
    }
1259
1260
    /**
1261
     * Allow search form to be accessible to schema
1262
     *
1263
     * @return Form
1264
     */
1265
    public function getFileSearchform()
1266
    {
1267
        return $this->fileSearchForm();
1268
    }
1269
}
1270