Completed
Pull Request — master (#491)
by Damian
02:35
created

AssetAdmin::remoteCreateForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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\Core\Injector\Injector;
28
use SilverStripe\Core\Manifest\ModuleLoader;
29
use SilverStripe\Forms\Form;
30
use SilverStripe\Forms\FormFactory;
31
use SilverStripe\ORM\ArrayList;
32
use SilverStripe\ORM\DataObject;
33
use SilverStripe\ORM\FieldType\DBHTMLText;
34
use SilverStripe\ORM\ValidationResult;
35
use SilverStripe\Security\Member;
36
use SilverStripe\Security\PermissionProvider;
37
use SilverStripe\Security\SecurityToken;
38
use SilverStripe\View\Requirements;
39
use SilverStripe\Versioned\Versioned;
40
use Exception;
41
42
/**
43
 * AssetAdmin is the 'file store' section of the CMS.
44
 * It provides an interface for manipulating the File and Folder objects in the system.
45
 */
46
class AssetAdmin extends LeftAndMain implements PermissionProvider
47
{
48
    private static $url_segment = 'assets';
0 ignored issues
show
Unused Code introduced by
The property $url_segment is not used and could be removed.

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

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
49
50
    private static $url_rule = '/$Action/$ID';
0 ignored issues
show
Unused Code introduced by
The property $url_rule is not used and could be removed.

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

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