Completed
Pull Request — master (#495)
by Simon
02:31
created

AssetAdmin::getRemoteCreateForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
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\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\Security;
38
use SilverStripe\Security\SecurityToken;
39
use SilverStripe\View\Requirements;
40
use SilverStripe\Versioned\Versioned;
41
use Exception;
42
43
/**
44
 * AssetAdmin is the 'file store' section of the CMS.
45
 * It provides an interface for manipulating the File and Folder objects in the system.
46
 */
47
class AssetAdmin extends LeftAndMain implements PermissionProvider
48
{
49
    private static $url_segment = 'assets';
0 ignored issues
show
Unused Code introduced by
The property $url_segment is not used and could be removed.

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

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

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

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
52
53
    private static $menu_title = 'Files';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $menu_title is not used and could be removed.

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

Loading history...
54
55
    private static $menu_icon_class = 'font-icon-image';
0 ignored issues
show
Unused Code introduced by
The property $menu_icon_class is not used and could be removed.

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

Loading history...
56
57
    private static $tree_class = Folder::class;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $tree_class is not used and could be removed.

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

Loading history...
58
59
    private static $url_handlers = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_handlers is not used and could be removed.

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

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