Completed
Push — master ( b8da20...856b73 )
by
unknown
02:45
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\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
        'fileSelectForm/$ID' => 'fileSelectForm',
75
    ];
76
77
    /**
78
     * Amount of results showing on a single page.
79
     *
80
     * @config
81
     * @var int
82
     */
83
    private static $page_length = 50;
0 ignored issues
show
Unused Code introduced by
The property $page_length is not used and could be removed.

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

Loading history...
84
85
    /**
86
     * @config
87
     * @see Upload->allowedMaxFileSize
88
     * @var int
89
     */
90
    private static $allowed_max_file_size;
0 ignored issues
show
Unused Code introduced by
The property $allowed_max_file_size is not used and could be removed.

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

Loading history...
91
92
    /**
93
     * @config
94
     *
95
     * @var int
96
     */
97
    private static $max_history_entries = 100;
0 ignored issues
show
Unused Code introduced by
The property $max_history_entries is not used and could be removed.

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

Loading history...
98
99
    /**
100
     * @var array
101
     */
102
    private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

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

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