Completed
Pull Request — master (#202)
by Ingo
02:38
created

AssetAdmin::getFileEditForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 1
eloc 7
nc 1
nop 1
1
<?php
2
3
namespace SilverStripe\AssetAdmin\Controller;
4
5
use SilverStripe\Filesystem\Storage\AssetNameGenerator;
6
use LeftAndMain;
7
use PermissionProvider;
8
use DateField;
9
use DropdownField;
10
use Controller;
11
use TextField;
12
use FieldList;
13
use Form;
14
use FormAction;
15
use CheckboxField;
16
use ArrayData;
17
use File;
18
use Session;
19
use Requirements;
20
use CMSBatchActionHandler;
21
use HiddenField;
22
use DataObject;
23
use Injector;
24
use Folder;
25
use Security;
26
use CMSForm;
27
use SS_List;
28
use SSViewer;
29
use HeaderField;
30
use FieldGroup;
31
use Object;
32
use SS_HTTPRequest;
33
use SS_HTTPResponse;
34
use Upload;
35
use Config;
36
37
/**
38
 * AssetAdmin is the 'file store' section of the CMS.
39
 * It provides an interface for manipulating the File and Folder objects in the system.
40
 *
41
 * @package cms
42
 * @subpackage assets
43
 */
44
class AssetAdmin extends LeftAndMain implements PermissionProvider
45
{
46
    private static $url_segment = 'assets';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_segment is not used and could be removed.

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

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

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

Loading history...
49
50
    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...
51
52
    private static $tree_class = 'Folder';
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...
53
54
    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...
55
        // Legacy redirect for SS3-style detail view
56
        'EditForm/field/File/item/$FileID/$Action' => 'legacyRedirectForEditView',
57
        // Pass all URLs to the index, for React to unpack
58
        'show/$FolderID/edit/$FileID' => 'index',
59
        // API access points with structured data
60
        'POST api/createFolder' => 'apiCreateFolder',
61
        'POST api/createFile' => 'apiCreateFile',
62
        'GET api/readFolder' => 'apiReadFolder',
63
        'PUT api/updateFolder' => 'apiUpdateFolder',
64
        'PUT api/updateFile' => 'apiUpdateFile',
65
        'DELETE api/delete' => 'apiDelete',
66
        'GET api/search' => 'apiSearch',
67
    ];
68
69
    /**
70
     * Amount of results showing on a single page.
71
     *
72
     * @config
73
     * @var int
74
     */
75
    private static $page_length = 15;
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...
76
77
    /**
78
     * @config
79
     * @see Upload->allowedMaxFileSize
80
     * @var int
81
     */
82
    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...
83
84
    /**
85
     * @var array
86
     */
87
    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...
88
        'legacyRedirectForEditView',
89
        'apiCreateFolder',
90
        'apiCreateFile',
91
        'apiReadFolder',
92
        'apiUpdateFolder',
93
        'apiUpdateFile',
94
        'apiDelete',
95
        'apiSearch',
96
    );
97
98
    /**
99
     * Set up the controller
100
     */
101
    public function init()
102
    {
103
        parent::init();
104
105
        Requirements::javascript(FRAMEWORK_DIR . "/admin/client/dist/js/bundle-lib.js");
106
        Requirements::add_i18n_javascript(ASSET_ADMIN_DIR . '/client/lang', false, true);
107
        Requirements::javascript(ASSET_ADMIN_DIR . "/client/dist/js/bundle.js");
108
        Requirements::css(ASSET_ADMIN_DIR . "/client/dist/styles/bundle.css");
109
110
        CMSBatchActionHandler::register('delete', 'SilverStripe\AssetAdmin\BatchAction\DeleteAssets', 'Folder');
111
    }
112
113
	public function getClientConfig() {
114
        $baseLink = $this->Link();
115
		return array_merge( parent::getClientConfig(), [
116
            'assetsRoute' => $this->Link() . ':folderAction?/:folderId?/:fileAction?/:fileId?',
117
            'assetsRouteHome' => $this->Link() . 'show/0',
118
            'createFileEndpoint' => [
119
                'url' => Controller::join_links($baseLink, 'api/createFile'),
120
                'method' => 'post',
121
                'payloadFormat' => 'urlencoded',
122
            ],
123
            'createFolderEndpoint' => [
124
                'url' => Controller::join_links($baseLink, 'api/createFolder'),
125
                'method' => 'post',
126
                'payloadFormat' => 'urlencoded',
127
            ],
128
            'readFolderEndpoint' => [
129
                'url' => Controller::join_links($baseLink, 'api/readFolder'),
130
                'method' => 'get',
131
                'responseFormat' => 'json',
132
            ],
133
            'searchEndpoint' => [
134
                'url' => Controller::join_links($baseLink, 'api/search'),
135
                'method' => 'get',
136
                'responseFormat' => 'json',
137
            ],
138
            'updateFileEndpoint' => [
139
                'url' => Controller::join_links($baseLink, 'api/updateFile'),
140
                'method' => 'put',
141
                'payloadFormat' => 'urlencoded',
142
            ],
143
            'updateFolderEndpoint' => [
144
                'url' => Controller::join_links($baseLink, 'api/updateFolder'),
145
                'method' => 'put',
146
                'payloadFormat' => 'urlencoded',
147
            ],
148
            'deleteEndpoint' => [
149
                'url' => Controller::join_links($baseLink, 'api/delete'),
150
                'method' => 'delete',
151
                'payloadFormat' => 'urlencoded',
152
            ],
153
            'limit' => $this->config()->page_length,
154
        ]);
155
	}
156
157
    /**
158
     * Fetches a collection of files by ParentID.
159
     *
160
     * @param SS_HTTPRequest $request
161
     * @return SS_HTTPResponse
162
     */
163
    public function apiReadFolder(SS_HTTPRequest $request)
164
    {
165
        $params = $request->requestVars();
166
        $items = array();
167
        $parentId = null;
0 ignored issues
show
Unused Code introduced by
$parentId 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...
168
        $folderID = null;
0 ignored issues
show
Unused Code introduced by
$folderID 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...
169
170
        if (!isset($params['id']) && !strlen($params['id'])) {
171
            $this->httpError(400);
172
        }
173
174
        $folderID = (int)$params['id'];
175
        if ($folderID === 0) {
176
            $folder = new Folder();
177
        } else {
178
            $folder = Folder::get()->byID($folderID);
179
        }
180
181
        // TODO Limit results to avoid running out of memory (implement client-side pagination)
182
        $files = $this->getList()->filter('ParentID', $folderID);
183
184
        if ($files) {
185
            foreach ($files as $file) {
186
                if (!$file->canView()) {
187
                    continue;
188
                }
189
190
                $items[] = $this->getObjectFromData($file);
191
            }
192
        }
193
194
        $response = new SS_HTTPResponse();
195
        $response->addHeader('Content-Type', 'application/json');
196
        $response->setBody(json_encode([
197
            'files' => $items,
198
            'count' => count($items),
199
            'parent' => $folder->ParentID, // grandparent
200
            'folderID' => $folderID,
201
            'canEdit' => $folder ? $folder->canEdit() : false,
202
            'canDelete' => $folder ? $folder->canDelete() : false
203
        ]));
204
205
        return $response;
206
    }
207
208
    /**
209
     * @param SS_HTTPRequest $request
210
     *
211
     * @return SS_HTTPResponse
212
     */
213
    public function apiSearch(SS_HTTPRequest $request)
214
    {
215
        $params = $request->getVars();
216
        $list = $this->getList($params);
217
218
        $response = new SS_HTTPResponse();
219
        $response->addHeader('Content-Type', 'application/json');
220
        $response->setBody(json_encode([
221
            // Serialisation
222
            "files" => array_map(function($file) {
223
                return $this->getObjectFromData($file);
224
            }, $list->toArray()),
225
            "count" => $list->count(),
226
        ]));
227
228
        return $response;
229
    }
230
231
    /**
232
     * @param SS_HTTPRequest $request
233
     *
234
     * @return SS_HTTPResponse
235
     */
236
    public function apiUpdateFile(SS_HTTPRequest $request)
237
    {
238
        parse_str($request->getBody(), $data);
239
240
        // TODO CSRF token check
241
242 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...
243
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
244
                ->addHeader('Content-Type', 'application/json');
245
        }
246
247
        $id = $data['id'];
248
        $record = $this->getList()->filter('ID', (int) $id)->first();
249
250 View Code Duplication
        if (!$record) {
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...
251
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
252
                ->addHeader('Content-Type', 'application/json');
253
        }
254
255 View Code Duplication
        if (!$record->canEdit()) {
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...
256
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
257
                ->addHeader('Content-Type', 'application/json');
258
        }
259
260
        // TODO Use same property names and capitalisation as DataObject
261
        if (!empty($data['title'])) {
262
            $record->Title = $data['title'];
263
        }
264
265
        // TODO Use same property names and capitalisation as DataObject
266
        if (!empty($data['basename'])) {
267
            $record->Name = $data['basename'];
268
        }
269
270
        $record->write();
271
272
        return (new SS_HTTPResponse(json_encode(['status' => 'ok']), 200))
273
            ->addHeader('Content-Type', 'application/json');
274
    }
275
276
    /**
277
     * @param SS_HTTPRequest $request
278
     *
279
     * @return SS_HTTPResponse
280
     */
281
    public function apiDelete(SS_HTTPRequest $request)
282
    {
283
        parse_str($request->getBody(), $vars);
284
285
        // TODO CSRF token check
286
287 View Code Duplication
        if (!isset($vars['ids']) || !$vars['ids']) {
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...
288
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
289
                ->addHeader('Content-Type', 'application/json');
290
        }
291
292
        $fileIds = $vars['ids'];
293
        $files = $this->getList()->filter("ID", $fileIds)->toArray();
294
295 View Code Duplication
        if (!count($files)) {
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...
296
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
297
                ->addHeader('Content-Type', 'application/json');
298
        }
299
300
        if (!min(array_map(function ($file) {
301
            return $file->canDelete();
302
        }, $files))) {
303
            return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
304
                ->addHeader('Content-Type', 'application/json');
305
        }
306
307
        foreach ($files as $file) {
308
            $file->delete();
309
        }
310
311
        return (new SS_HTTPResponse(json_encode(['status' => 'file was deleted'])))
312
            ->addHeader('Content-Type', 'application/json');
313
    }
314
315
    public function apiCreateFile(SS_HTTPRequest $request)
316
    {
317
        $class = 'File';
318
        $data = $request->postVars();
319
        $upload = $this->getUpload();
320
321
        // TODO CSRF token check
322
323
        // check create permissions
324
        if (!singleton($class)->canCreate()) {
325
            return Security::permissionFailure($this);
326
        }
327
328
        // check canAddChildren permissions
329 View Code Duplication
        if (!empty($data['folderId']) && is_numeric($data['folderId'])) {
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...
330
            $parentRecord = Folder::get()->byID($data['folderId']);
331
            if ($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) {
332
                return Security::permissionFailure($this);
333
            }
334
        } else {
335
            $parentRecord = null;
336
        }
337
338
        $tmpFile = $request->postVar('Upload');
339
        if(!$upload->validate($tmpFile)) {
340
            $result = ['error' => $upload->getErrors()];
341
            return (new SS_HTTPResponse(json_encode($result), 400))
342
                ->addHeader('Content-Type', 'application/json');
343
        }
344
345
        // TODO Allow batch uploads
346
347
        $file = File::create();
348
        $uploadResult = $upload->loadIntoFile($tmpFile, $file, $parentRecord ? $parentRecord->getFilename() : '/');
349 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...
350
            $result = ['error' => 'unknown'];
351
            return (new SS_HTTPResponse(json_encode($result), 400))
352
                ->addHeader('Content-Type', 'application/json');
353
        }
354
355
        $result = [$this->getObjectFromData($file)];
356
357
        return (new SS_HTTPResponse(json_encode($result)))
358
            ->addHeader('Content-Type', 'application/json');
359
    }
360
361
    public function apiCreateFolder(SS_HTTPRequest $request)
362
    {
363
        parse_str($request->getBody(), $data);
364
365
        $class = 'Folder';
366
367
        // TODO CSRF token check
368
369
        // check create permissions
370
        if (!singleton($class)->canCreate()) {
371
            return Security::permissionFailure($this);
372
        }
373
374
        // check addchildren permissions
375 View Code Duplication
        if (!empty($data['ParentID']) && is_numeric($data['ParentID'])) {
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...
376
            $parentRecord = \DataObject::get_by_id($class, $data['ParentID']);
377
            if ($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) {
378
                return Security::permissionFailure($this);
379
            }
380
        } else {
381
            $parentRecord = null;
382
        }
383
        $data['ParentID'] = ($parentRecord && $parentRecord->ID) ? (int)$parentRecord->ID : 0;
384
385
        // Build filename
386
        $baseFilename = isset($data['Name'])
387
            ? basename($data['Name'])
388
            : _t('AssetAdmin.NEWFOLDER', "NewFolder");
389
390
        if ($parentRecord && $parentRecord->ID) {
391
            $baseFilename = $parentRecord->getFilename() . '/' . $baseFilename;
392
        }
393
394
        // Ensure name is unique
395
        $nameGenerator = $this->getNameGenerator($baseFilename);
396
        foreach ($nameGenerator as $filename) {
397
            if (! File::find($filename)) {
398
                break;
399
            }
400
        }
401
        $data['Name'] = basename($filename);
0 ignored issues
show
Bug introduced by
The variable $filename seems to be defined by a foreach iteration on line 396. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
402
403
        // Create record
404
        $record = $class::create();
405
        $record->ParentID = $data['ParentID'];
406
        $record->Name = $record->Title = basename($data['Name']);
407
        $record->write();
408
409
        $result = [
410
            "ParentID" => $record->ParentID,
411
            "ID" => $record->ID,
412
            "Filename" => $record->Filename,
413
        ];
414
415
        return (new SS_HTTPResponse(json_encode($result)))->addHeader('Content-Type', 'application/json');
416
    }
417
418
    public function legacyRedirectForEditView($request)
419
    {
420
        $fileID = $request->param('FileID');
421
        $file = File::get()->byID($fileID);
422
        $link = $this->getFileEditLink($file) ?: $this->Link();
0 ignored issues
show
Documentation introduced by
$file is of type object<DataObject>|null, but the function expects a object<File>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
423
        $this->redirect($link);
424
    }
425
426
    /**
427
     * Given a file return the CMS link to edit it
428
     *
429
     * @param File $file
430
     * @return string
431
     */
432
    public function getFileEditLink($file) {
433
        if(!$file || !$file->isInDB()) {
434
            return null;
435
        }
436
437
        return Controller::join_links(
438
            $this->Link('show'),
439
            $file->ParentID,
440
            'edit',
441
            $file->ID
442
        );
443
    }
444
445
    /**
446
     * Get the search context from {@link File}, used to create the search form
447
     * as well as power the /search API endpoint.
448
     *
449
     * @return SearchContext
450
     */
451
    public function getSearchContext()
452
    {
453
        $context = singleton('File')->getDefaultSearchContext();
454
455
        // Customize fields
456
        $dateHeader = HeaderField::create('Date', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
457
        $dateFrom = DateField::create('CreatedFrom', _t('CMSSearch.FILTERDATEFROM', 'From'))
458
        ->setConfig('showcalendar', true);
459
        $dateTo = DateField::create('CreatedTo', _t('CMSSearch.FILTERDATETO', 'To'))
460
        ->setConfig('showcalendar', true);
461
        $dateGroup = FieldGroup::create(
462
            $dateHeader,
463
            $dateFrom,
464
            $dateTo
465
        );
466
        $context->addField($dateGroup);
467
        $appCategories = array(
468
            'archive' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
469
            'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
470
            'document' => _t('AssetAdmin.AppCategoryDocument', 'Document'),
471
            'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
472
            'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
473
            'video' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
474
        );
475
        $context->addField(
476
            $typeDropdown = new DropdownField(
477
                'AppCategory',
478
                _t('AssetAdmin.Filetype', 'File type'),
479
                $appCategories
480
            )
481
        );
482
483
        $typeDropdown->setEmptyString(' ');
484
485
        $context->addField(
486
            new CheckboxField('CurrentFolderOnly', _t('AssetAdmin.CurrentFolderOnly', 'Limit to current folder?'))
487
        );
488
        $context->getFields()->removeByName('Title');
489
490
        return $context;
491
    }
492
493
    /**
494
     * Get an asset renamer for the given filename.
495
     *
496
     * @param  string             $filename Path name
497
     * @return AssetNameGenerator
498
     */
499
    protected function getNameGenerator($filename)
500
    {
501
        return Injector::inst()
502
            ->createWithArgs('AssetNameGenerator', array($filename));
503
    }
504
505
    /**
506
     * @todo Implement on client
507
     */
508
    public function breadcrumbs($unlinked = false)
509
    {
510
        return null;
511
    }
512
513
514
    /**
515
     * Don't include class namespace in auto-generated CSS class
516
     */
517
    public function baseCSSClasses()
518
    {
519
        return 'AssetAdmin LeftAndMain';
520
    }
521
522
    /**
523
     * Don't include class namespace in template names
524
     * @todo Make code in framework more namespace-savvy so that we don't need this duplication
525
     */
526
    public function getTemplatesWithSuffix($suffix)
527
    {
528
        $className = get_class($this);
529
        $baseClass = 'LeftandMain';
530
531
        $templates = array();
532
        $classes = array_reverse(\ClassInfo::ancestry($className));
533
        foreach ($classes as $class) {
534
            $template = (new \ReflectionClass($class))->getShortName() . $suffix;
535
            if (\SSViewer::hasTemplate($template)) {
0 ignored issues
show
Documentation introduced by
$template is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
536
                $templates[] = $template;
537
            }
538
539
            // If the class is "Page_Controller", look for Page.ss
540
            if (stripos($class, '_controller') !== false) {
541
                $template = str_ireplace('_controller', '', $class) . $suffix;
542
                if (\SSViewer::hasTemplate($template)) {
0 ignored issues
show
Documentation introduced by
$template is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
543
                    $templates[] = $template;
544
                }
545
            }
546
547
            if ($baseClass && $class == $baseClass) {
548
                break;
549
            }
550
        }
551
552
        return $templates;
553
    }
554
555
    public function providePermissions()
556
    {
557
        $title = _t("AssetAdmin.MENUTITLE", LeftAndMain::menu_title_for_class($this->class));
0 ignored issues
show
Deprecated Code introduced by
The method LeftAndMain::menu_title_for_class() has been deprecated with message: 5.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
558
559
        return array(
560
            "CMS_ACCESS_AssetAdmin" => array(
561
                'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)),
0 ignored issues
show
Documentation introduced by
array('title' => $title) is of type array<string,string,{"title":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
562
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
563
            )
564
        );
565
    }
566
567
    /**
568
     * The form is used to generate a form schema,
569
     * as well as an intermediary object to process data through API endpoints.
570
     * Since it's used directly on API endpoints, it does not have any form actions.
571
     *
572
     * @param Folder
573
     * @return Form
574
     */
575
    protected function getFolderEditForm(Folder $folder)
576
    {
577
        $form = Form::create(
578
            $this,
579
            'FolderEditForm',
580
            $folder->getCMSFields(),
581
            FieldList::create()
582
        );
583
584
        return $form;
585
    }
586
587
    /**
588
     * See {@link getFolderEditForm()} for details.
589
     *
590
     * @param File $file
591
     * @return Form
592
     */
593
    protected function getFileEditForm(File $file)
594
    {
595
        $form = Form::create(
596
            $this,
597
            'FileEditForm',
598
            $file->getCMSFields(),
599
            FieldList::create()
600
        );
601
602
        return $form;
603
    }
604
605
    /**
606
     * @param File $file
607
     *
608
     * @return array
609
     */
610
    protected function getObjectFromData(File $file)
611
    {
612
        $object = array(
613
            'id' => $file->ID,
614
            'created' => $file->Created,
615
            'lastUpdated' => $file->LastEdited,
616
            'owner' => null,
617
            'parent' => null,
618
            'attributes' => array(
619
                'dimensions' => array(),
620
            ),
621
            'title' => $file->Title,
622
            'type' => $file->is_a('Folder') ? 'folder' : $file->FileType,
623
            'category' => $file->is_a('Folder') ? 'folder' : $file->appCategory(),
624
            'name' => $file->Name,
625
            'filename' => $file->Filename,
626
            'extension' => $file->Extension,
0 ignored issues
show
Bug introduced by
The property Extension does not seem to exist. Did you mean allowed_extensions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
627
            'size' => $file->Size,
628
            'url' => $file->AbsoluteURL,
629
            'canEdit' => $file->canEdit(),
630
            'canDelete' => $file->canDelete()
631
        );
632
633
        /** @var Member $owner */
634
        $owner = $file->Owner();
635
636
        if ($owner) {
637
            $object['owner'] = array(
638
                'id' => $owner->ID,
639
                'title' => trim($owner->FirstName . ' ' . $owner->Surname),
640
            );
641
        }
642
643
        /** @var Folder $parent */
644
        $parent = $file->Parent();
645
646
        if ($parent) {
647
            $object['parent'] = array(
648
                'id' => $parent->ID,
649
                'title' => $parent->Title,
650
                'filename' => $parent->Filename,
651
            );
652
        }
653
654
        /** @var File $file */
655
        if ($file->hasMethod('getWidth') && $file->hasMethod('getHeight')) {
656
            $object['attributes']['dimensions']['width'] = $file->Width;
657
            $object['attributes']['dimensions']['height'] = $file->Height;
0 ignored issues
show
Bug introduced by
The property Height does not seem to exist. Did you mean cms_thumbnail_height?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
658
        }
659
660
        return $object;
661
    }
662
663
664
    /**
665
     * Returns the files and subfolders contained in the currently selected folder,
666
     * defaulting to the root node. Doubles as search results, if any search parameters
667
     * are set through {@link SearchForm()}.
668
     *
669
     * @param array $params Unsanitised request parameters
670
     * @return SS_List
671
     */
672
    protected function getList($params = array())
673
    {
674
        $context = $this->getSearchContext();
675
676
        // Overwrite name filter to search both Name and Title attributes
677
        $context->removeFilterByName('Name');
678
679
        // Lazy loaded list. Allows adding new filters through SearchContext.
680
        $list = $context->getResults($params);
681
682
        // Re-add previously removed "Name" filter as combined filter
683
        // TODO Replace with composite SearchFilter once that API exists
684
        if(!empty($params['Name'])) {
685
            $list = $list->filterAny(array(
686
                'Name:PartialMatch' => $params['Name'],
687
                'Title:PartialMatch' => $params['Name']
688
            ));
689
        }
690
691
        // Optionally limit search to a folder (non-recursive)
692
        if(!empty($params['ParentID']) && is_numeric($params['ParentID'])) {
693
            $list = $list->filter('ParentID', $params['ParentID']);
694
        }
695
696
        // Date filtering
697
        if (!empty($params['CreatedFrom'])) {
698
            $fromDate = new DateField(null, null, $params['CreatedFrom']);
699
            $list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
700
        }
701
        if (!empty($params['CreatedTo'])) {
702
            $toDate = new DateField(null, null, $params['CreatedTo']);
703
            $list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
704
        }
705
706
        // Categories
707
        if (!empty($filters['AppCategory']) && !empty(File::config()->app_categories[$filters['AppCategory']])) {
0 ignored issues
show
Bug introduced by
The variable $filters seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
708
            $extensions = File::config()->app_categories[$filters['AppCategory']];
709
            $list = $list->filter('Name:PartialMatch', $extensions);
710
        }
711
712
        // Sort folders first
713
        $list = $list->sort(
714
            '(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"'
715
        );
716
717
        // Pagination
718
        if (isset($filters['page']) && isset($filters['limit'])) {
719
            $page = $filters['page'];
720
            $limit = $filters['limit'];
721
            $offset = ($page - 1) * $limit;
722
            $list = $list->limit($limit, $offset);
723
        }
724
725
        // Access checks
726
        $list = $list->filterByCallback(function($file) {return $file->canView();});
727
728
        return $list;
729
    }
730
731
    /**
732
     * @return Upload
733
     */
734
    protected function getUpload()
735
    {
736
        $upload = Upload::create();
737
        $upload->getValidator()->setAllowedExtensions(
738
            // filter out '' since this would be a regex problem on JS end
739
            array_filter(Config::inst()->get('File', 'allowed_extensions'))
740
        );
741
742
        return $upload;
743
    }
744
}
745