Passed
Push — master ( b0afa4...2243e7 )
by Nicolaas
02:46
created

View::Form()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Sunnysideup\AssetsOverview\Control;
4
5
use SilverStripe\CMS\Controllers\ContentController;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Control\Middleware\HTTPCacheControlMiddleware;
10
use SilverStripe\Core\Environment;
11
use SilverStripe\Core\Flushable;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\Forms\CheckboxSetField;
14
use SilverStripe\Forms\DropdownField;
15
use SilverStripe\Forms\FieldList;
16
use SilverStripe\Forms\Form;
17
use SilverStripe\Forms\FormAction;
18
use SilverStripe\Forms\HiddenField;
19
use SilverStripe\Forms\OptionsetField;
20
use SilverStripe\Forms\TextField;
21
use SilverStripe\ORM\ArrayList;
22
use SilverStripe\ORM\FieldType\DBField;
23
use SilverStripe\Security\Permission;
24
use SilverStripe\Security\Security;
25
use SilverStripe\Versioned\Versioned;
26
use SilverStripe\View\ArrayData;
27
use SilverStripe\View\Requirements;
28
use SilverStripe\View\SSViewer;
29
use Sunnysideup\AssetsOverview\Api\AddAndRemoveFromDb;
30
use Sunnysideup\AssetsOverview\Files\AllFilesInfo;
31
use Sunnysideup\AssetsOverview\Files\OneFileInfo;
32
use Sunnysideup\AssetsOverview\Traits\FilesystemRelatedTraits;
33
34
/**
35
 * Class \Sunnysideup\AssetsOverview\Control\View
36
 *
37
 * @property \Sunnysideup\AssetsOverview\Control\View $dataRecord
38
 * @method \Sunnysideup\AssetsOverview\Control\View data()
39
 * @mixin \Sunnysideup\AssetsOverview\Control\View
40
 */
41
class View extends ContentController implements Flushable
42
{
43
    use FilesystemRelatedTraits;
44
45
    public static function get_sorters()
46
    {
47
        return self::SORTERS;
48
    }
49
50
    public static function get_filters()
51
    {
52
        return self::FILTERS;
53
    }
54
55
    public static function get_displayers()
56
    {
57
        return self::DISPLAYERS;
58
    }
59
60
61
    protected static $allFilesProvider = null;
62
63
    /**
64
     * @var string
65
     */
66
    protected string $title = '';
67
68
69
    /**
70
     * @var int
71
     */
72
    protected int $limit = 1000;
73
74
    /**
75
     * @var int
76
     */
77
    protected int $startLimit = 0;
78
79
    /**
80
     * @var int
81
     */
82
    protected int $endLimit = 0;
83
84
    /**
85
     * @var int
86
     */
87
    protected int $pageNumber = 1;
88
89
    /**
90
     * @var string
91
     */
92
    protected string $sorter = 'byfolder';
93
94
    /**
95
     * @var string
96
     */
97
    protected string $filter = '';
98
99
    /**
100
     * @var string
101
     */
102
    protected string $displayer = 'thumbs';
103
104
    /**
105
     * @var array
106
     */
107
    protected array $allowedExtensions = [];
108
109
    /**
110
     * Defines methods that can be called directly.
111
     *
112
     * @var array
113
     */
114
    private static $allowed_actions = [
115
        'index' => 'ADMIN',
116
        'json' => 'ADMIN',
117
        'jsonfull' => 'ADMIN',
118
        'sync' => 'ADMIN',
119
        'addtodb' => 'ADMIN',
120
        'removefromdb' => 'ADMIN',
121
    ];
122
123
    public static function flush()
124
    {
125
        AllFilesInfo::flushCache();
126
    }
127
128
    public function Link($action = null)
129
    {
130
        $str = Director::absoluteURL(DIRECTORY_SEPARATOR . 'admin/assets-overview');
131
        if ($action) {
132
            $str .= DIRECTORY_SEPARATOR . $action;
133
        }
134
135
        return $str;
136
    }
137
138
    public function getTitle(): string
139
    {
140
        $this->getSortStatement();
141
        $this->getFilterStatement();
142
        $this->getPageStatement();
143
144
        if ($this->hasFilter()) {
145
            $filterStatement = '' .
146
                $this->getTotalFileCountFiltered() . ' files / ' .
147
                $this->getTotalFileSizeFiltered();
148
        } else {
149
            $filterStatement =
150
                $this->getTotalFileCountRaw() . ' / ' .
151
                $this->getTotalFileSizeRaw();
152
        }
153
154
        return DBField::create_field(
155
            'HTMLText',
156
            'Found ' . $filterStatement
157
        );
158
    }
159
160
    public function getSubTitle(): string
161
    {
162
        $array = array_filter(
163
            [
164
                $this->getSortStatement(),
165
                $this->getFilterStatement(),
166
                $this->getPageStatement(),
167
                $this->getTotalsStatement(),
168
            ]
169
        );
170
171
        return DBField::create_field(
172
            'HTMLText',
173
            '- ' . implode('<br /> - ', $array)
174
        );
175
    }
176
177
    public function getSortStatement(): string
178
    {
179
        return '<strong>sorted by</strong>: ' . self::SORTERS[$this->sorter]['Title'] ?? 'ERROR IN SORTER';
180
    }
181
182
    public function getFilterStatement(): string
183
    {
184
        $filterArray = array_filter(
185
            [
186
                self::FILTERS[$this->filter]['Title'] ?? '',
187
                implode(', ', $this->allowedExtensions),
188
            ]
189
        );
190
191
        return count($filterArray) ? '<strong>filtered for</strong>: ' . implode(', ', $filterArray) : '';
192
    }
193
194
    public function getPageStatement(): string
195
    {
196
        return $this->getNumberOfPages() > 1 ?
197
            '<strong>page</strong>: ' . $this->pageNumber . ' of ' . $this->getNumberOfPages() . ', showing file ' . ($this->startLimit + 1) . ' to ' . $this->endLimit
198
            :
199
            '';
200
    }
201
202
    public function getDisplayer(): string
203
    {
204
        return $this->displayer;
205
    }
206
207
    public function getFilesAsArrayList(): ArrayList
208
    {
209
        return $this->getAllFilesInfoProvider()->getFilesAsArrayList();
210
    }
211
212
    public function getFilesAsSortedArrayList(): ArrayList
213
    {
214
        return $this->getAllFilesInfoProvider()->getFilesAsSortedArrayList();
215
    }
216
217
    public function getTotalFileCountRaw(): string
218
    {
219
        return (string) number_format($this->getAllFilesInfoProvider()->getTotalFileCountRaw());
220
    }
221
222
    public function getTotalFileCountFiltered(): string
223
    {
224
        return (string) number_format($this->getAllFilesInfoProvider()->getTotalFileCountFiltered());
225
    }
226
227
    public function getTotalFileSizeFiltered(): string
228
    {
229
        return (string) $this->humanFileSize($this->getAllFilesInfoProvider()->getTotalFileSizeFiltered());
230
    }
231
232
    public function getTotalFileSizeRaw(): string
233
    {
234
        return (string) $this->humanFileSize($this->getAllFilesInfoProvider()->getTotalFileSizesRaw());
235
    }
236
237
    public function index($request)
238
    {
239
        if ('rawlistfull' === $this->displayer) {
240
            $this->addMapToItems();
241
        }
242
243
        // if (false === AllFilesInfo::loadedFromCache()) {
244
        //     $url = $_SERVER['REQUEST_URI'];
245
        //     $url = str_replace('flush=', 'previousflush=', $url);
246
247
        //     $js = '<script>window.location.href = \'' . $url . '\';</script>';
248
        //     return 'go to <a href="' . $url . '">' . $url . '</a> if this page does not autoload';
249
        // }
250
251
252
        return $this->renderWith('AssetsOverview');
253
    }
254
255
    public function json($request)
256
    {
257
        return $this->sendJSON($this->getRawData());
258
    }
259
260
    public function jsonfull($request)
261
    {
262
        $array = [];
263
264
        foreach ($this->getFilesAsArrayList()->toArray() as $item) {
265
            $array[] = $item->toMap();
266
        }
267
268
        return $this->sendJSON($array);
269
    }
270
271
    public function sync()
272
    {
273
        $array = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $array is dead and can be removed.
Loading history...
274
        foreach ($this->getFilesAsArrayList()->toArray() as $item) {
275
            $obj = Injector::inst()->get(AddAndRemoveFromDb::class);
276
            $obj->run($item->toMap());
277
        }
278
    }
279
280
    public function addtodb()
281
    {
282
        foreach ($this->getFilesAsArrayList()->toArray() as $item) {
283
            $obj = Injector::inst()->get(AddAndRemoveFromDb::class);
284
            $obj->run($item->toMap(), 'add');
285
        }
286
    }
287
288
    public function removefromdb()
289
    {
290
        foreach ($this->filesAsArrayList->toArray() as $item) {
0 ignored issues
show
Bug Best Practice introduced by
The property filesAsArrayList does not exist on Sunnysideup\AssetsOverview\Control\View. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug introduced by
The method toArray() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

290
        foreach ($this->filesAsArrayList->/** @scrutinizer ignore-call */ toArray() as $item) {

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...
291
            $obj = Injector::inst()->get(AddAndRemoveFromDb::class);
292
            $obj->run($item->toMap(), 'remove');
293
        }
294
    }
295
296
    public function addMapToItems()
297
    {
298
        $this->isThumbList = false;
0 ignored issues
show
Bug Best Practice introduced by
The property isThumbList does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
299
        foreach ($this->getFilesAsSortedArrayList() as $group) {
300
            foreach ($group->Items as $item) {
301
                $map = $item->toMap();
302
                $item->FullFields = ArrayList::create();
303
                foreach ($map as $key => $value) {
304
                    if (false === $value) {
305
                        $value = 'no';
306
                    }
307
308
                    if (true === $value) {
309
                        $value = 'yes';
310
                    }
311
312
                    $item->FullFields->push(ArrayData::create(['Key' => $key, 'Value' => $value]));
313
                }
314
            }
315
        }
316
    }
317
318
    //#############################################
319
    // FORM
320
    //#############################################
321
    public function Form()
322
    {
323
        return $this->getForm();
324
    }
325
326
    protected function init()
327
    {
328
        parent::init();
329
        if (! Permission::check('ADMIN')) {
330
            return Security::permissionFailure($this);
331
        }
332
333
        Requirements::clear();
334
        ini_set('memory_limit', '1024M');
335
        Environment::increaseMemoryLimitTo();
336
        Environment::increaseTimeLimitTo(7200);
337
        SSViewer::config()->set('theme_enabled', false);
338
        Versioned::set_stage(Versioned::DRAFT);
339
        $this->getGetVariables();
340
        $this->getAllFilesInfoProvider();
341
    }
342
343
    protected function getTotalsStatement()
344
    {
345
        return $this->hasFilter() ? '<strong>Totals</strong>: ' .
346
            $this->getTotalFileCountRaw() . ' files / ' . $this->getTotalFileSizeRaw()
347
            : '';
348
    }
349
350
    protected function hasFilter(): bool
351
    {
352
        return $this->filter || count($this->allowedExtensions);
353
    }
354
355
    protected function getGetVariables()
356
    {
357
        $filter = $this->request->getVar('filter');
358
        if ($filter) {
359
            $this->filter = $filter;
360
        }
361
362
        $sorter = $this->request->getVar('sorter');
363
        if ($sorter) {
364
            $this->sorter = $sorter;
365
        }
366
367
        $displayer = $this->request->getVar('displayer');
368
        if ($displayer) {
369
            $this->displayer = $displayer;
370
        }
371
372
        $extensions = $this->request->getVar('extensions');
373
        if ($extensions) {
374
            if (! is_array($extensions)) {
375
                $extensions = [$extensions];
376
            }
377
378
            $this->allowedExtensions = $extensions;
379
            //make sure all are valid!
380
            $this->allowedExtensions = array_filter($this->allowedExtensions);
381
        }
382
383
        $limit = $this->request->getVar('limit');
384
        if ($limit) {
385
            $this->limit = $limit;
386
        }
387
388
        $this->pageNumber = ($this->request->getVar('page') ?: 1);
389
        $this->startLimit = $this->limit * ($this->pageNumber - 1);
390
        $this->endLimit = $this->limit * ($this->pageNumber + 0);
391
        $this->getAllFilesInfoProvider();
392
    }
393
394
    protected function sendJSON($data)
395
    {
396
        $json = json_encode($data, JSON_PRETTY_PRINT);
397
        if ($this->request->getVar('download')) {
398
            return HTTPRequest::send_file($json, 'files.json', 'text/json');
399
        }
400
        $response = (new HTTPResponse($json));
401
        $response->addHeader('Content-Type', 'application/json; charset="utf-8"');
402
        $response->addHeader('Pragma', 'no-cache');
403
        $response->addHeader('cache-control', 'no-cache, no-store, must-revalidate');
404
        $response->addHeader('Access-Control-Allow-Origin', '*');
405
        $response->addHeader('Expires', 0);
406
        HTTPCacheControlMiddleware::singleton()
407
            ->disableCache()
408
        ;
409
        $response->output();
410
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
411
    }
412
413
414
415
416
    protected function getForm(): Form
417
    {
418
        $fieldList = FieldList::create(
419
            [
420
                $this->createFormField('sorter', 'Sort by', $this->sorter, $this->getSorterList()),
421
                $this->createFormField('filter', 'Filter for errors', $this->filter, $this->getFilterList()),
422
                $this->createFormField('extensions', 'Filter by extensions', $this->allowedExtensions, $this->getExtensionList()),
423
                $this->createFormField('displayer', 'Displayed by', $this->displayer, $this->getDisplayerList()),
424
                $this->createFormField('limit', 'Items per page', $this->limit, $this->getLimitList()),
425
                $this->createFormField('page', 'Page number', $this->pageNumber, $this->getPageNumberList()),
426
                // TextField::create('compare', 'Compare With')->setDescription('add a link to a comparison file - e.g. http://oldsite.com/admin/assets-overview/test.json'),
427
            ]
428
        );
429
        $actionList = FieldList::create(
430
            [
431
                FormAction::create('index', 'Update File List'),
432
            ]
433
        );
434
435
        $form = Form::create($this, 'index', $fieldList, $actionList);
436
        $form->setFormMethod('GET', true);
437
        $form->disableSecurityToken();
438
439
        return $form;
440
    }
441
442
    protected function createFormField(string $name, string $title, $value, ?array $list = [])
443
    {
444
        $listCount = count($list);
445
        if (0 === $listCount) {
446
            $type = HiddenField::class;
447
        } elseif ('limit' === $name || 'page' === $name) {
448
            $type = DropdownField::class;
449
        } elseif ('extensions' === $name) {
450
            $type = CheckboxSetField::class;
451
        } elseif ($listCount < 20) {
452
            $type = OptionsetField::class;
453
        } else {
454
            $type = DropdownField::class;
455
        }
456
457
        $field = $type::create($name, $title)
458
            ->setValue($value);
459
        if ($listCount) {
460
            $field->setSource($list);
461
        }
462
463
        // $field->setAttribute('onchange', 'this.form.submit()');
464
465
        return $field;
466
    }
467
468
    protected function getSorterList(): array
469
    {
470
        $array = [];
471
        foreach (self::SORTERS as $key => $data) {
472
            $array[$key] = $data['Title'];
473
        }
474
475
        return $array;
476
    }
477
478
    protected function getFilterList(): array
479
    {
480
        $array = ['' => '-- no filter --'];
481
        foreach (self::FILTERS as $key => $data) {
482
            $array[$key] = $data['Title'];
483
        }
484
485
        return $array;
486
    }
487
488
    protected function getDisplayerList(): array
489
    {
490
        return self::DISPLAYERS;
491
    }
492
493
    protected function getExtensionList(): array
494
    {
495
        $list = array_filter($this->getAllFilesInfoProvider()->getAvailableExtensions());
496
        $list = ['n/a' => 'n/a'] + $list;
497
        return $list;
498
    }
499
500
    protected function getPageNumberList(): array
501
    {
502
        $list = range(1, $this->getNumberOfPages());
503
        $list = array_combine($list, $list);
504
        $list[(string) $this->pageNumber] = (string) $this->pageNumber;
505
        if (count($list) < 2) {
506
            return [];
507
        }
508
509
        return $list;
510
    }
511
512
    protected function getNumberOfPages(): int
513
    {
514
        return ceil($this->getAllFilesInfoProvider()->getTotalFileCountFiltered() / $this->limit);
0 ignored issues
show
Bug Best Practice introduced by
The expression return ceil($this->getAl...tered() / $this->limit) returns the type double which is incompatible with the type-hinted return integer.
Loading history...
515
    }
516
517
    protected function getLimitList(): array
518
    {
519
        $step = 100;
520
        $array = [];
521
        $i = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $i is dead and can be removed.
Loading history...
522
        if ($this->getAllFilesInfoProvider()->getTotalFileCountRaw() > $step) {
523
            for ($i = $step; ($i - $step) < $this->totalFileCountFiltered; $i += $step) {
0 ignored issues
show
Bug Best Practice introduced by
The property totalFileCountFiltered does not exist on Sunnysideup\AssetsOverview\Control\View. Since you implemented __get, consider adding a @property annotation.
Loading history...
524
                if ($i > $this->limit && ! isset($array[$this->limit])) {
525
                    $array[$this->limit] = $this->limit;
526
                }
527
528
                $array[$i] = $i;
529
            }
530
        }
531
532
        return $array;
533
    }
534
535
536
537
    protected function getRawData(): array
538
    {
539
        return $this->getAllFilesInfoProvider()->toArray();
540
    }
541
542
    protected function getAllFilesInfoProvider(): AllFilesInfo
543
    {
544
        if (!self::$allFilesProvider) {
545
            /** @var AllFilesInfo self::$allFilesProvider */
546
            self::$allFilesProvider = AllFilesInfo::inst();
547
            self::$allFilesProvider
548
                ->setFilters(self::get_filters())
549
                ->setSorters(self::get_sorters())
550
                ->setFilter($this->filter)
551
                ->setAllowedExtensions($this->allowedExtensions)
552
                ->setSorter($this->sorter)
553
                ->setLimit($this->limit)
554
                ->setPageNumber($this->pageNumber)
555
                ->setStartLimit($this->startLimit)
556
                ->setEndLimit($this->endLimit)
557
                ->setDisplayer($this->displayer)
558
                ->getFilesAsArrayList();
559
            while ($this->startLimit > $this->filesAsArrayList->count()) {
0 ignored issues
show
Bug Best Practice introduced by
The property filesAsArrayList does not exist on Sunnysideup\AssetsOverview\Control\View. Since you implemented __get, consider adding a @property annotation.
Loading history...
560
                $this->pageNumber--;
561
                $this->startLimit = $this->limit * ($this->pageNumber - 1);
562
                $this->endLimit = $this->limit * ($this->pageNumber + 0);
563
            }
564
        }
565
        return self::$allFilesProvider;
566
    }
567
568
569
    private const SORTERS = [
570
        'byfolder' => [
571
            'Title' => 'Folder',
572
            'Sort' => 'PathFolderFromAssets',
573
            'Group' => 'PathFolderFromAssets',
574
        ],
575
        'byfilename' => [
576
            'Title' => 'Filename',
577
            'Sort' => 'PathFileName',
578
            'Group' => 'PathFileNameFirstLetter',
579
        ],
580
        'bydbtitle' => [
581
            'Title' => 'Database Title',
582
            'Sort' => 'DBTitle',
583
            'Group' => 'DBTitleFirstLetter',
584
        ],
585
        'byfilesize' => [
586
            'Title' => 'Filesize',
587
            'Sort' => 'PathFileSize',
588
            'Group' => 'HumanFileSizeRounded',
589
        ],
590
        'bylastedited' => [
591
            'Title' => 'Last Edited',
592
            'Sort' => 'DBLastEditedTS',
593
            'Group' => 'DBLastEdited',
594
        ],
595
        'byextension' => [
596
            'Title' => 'PathExtension',
597
            'Sort' => 'PathExtensionAsLower',
598
            'Group' => 'PathExtensionAsLower',
599
        ],
600
        'byisimage' => [
601
            'Title' => 'Image vs Other Files',
602
            'Sort' => 'ImageIsImage',
603
            'Group' => 'HumanImageIsImage',
604
        ],
605
        'byclassname' => [
606
            'Title' => 'Class Name',
607
            'Sort' => 'DBClassName',
608
            'Group' => 'DBClassName',
609
        ],
610
        'bydimensions' => [
611
            'Title' => 'Dimensions (small to big)',
612
            'Sort' => 'ImagePixels',
613
            'Group' => 'HumanImageDimensions',
614
        ],
615
        'byratio' => [
616
            'Title' => 'ImageRatio',
617
            'Sort' => 'ImageRatio',
618
            'Group' => 'ImageRatio',
619
        ],
620
    ];
621
622
    private const FILTERS = [
623
        'byanyerror' => [
624
            'Title' => 'Any Error',
625
            'Field' => 'ErrorHasAnyError',
626
            'Values' => [1, true],
627
        ],
628
        'byfilesystemstatus' => [
629
            'Title' => 'Not in filesystem',
630
            'Field' => 'ErrorIsInFileSystem',
631
            'Values' => [1, true],
632
        ],
633
        'bymissingfromdatabase' => [
634
            'Title' => 'Not in database',
635
            'Field' => 'ErrorDBNotPresent',
636
            'Values' => [1, true],
637
        ],
638
        'bymissingfromlive' => [
639
            'Title' => 'Not on live site',
640
            'Field' => 'ErrorDBNotPresentLive',
641
            'Values' => [1, true],
642
        ],
643
        'bymissingfromstaging' => [
644
            'Title' => 'Not on draft site',
645
            'Field' => 'ErrorDBNotPresentStaging',
646
            'Values' => [1, true],
647
        ],
648
        'bydraftonly' => [
649
            'Title' => 'In draft only (not on live)',
650
            'Field' => 'ErrorInDraftOnly',
651
            'Values' => [1, true],
652
        ],
653
        'byliveonly' => [
654
            'Title' => 'On live only (not in draft)',
655
            'Field' => 'ErrorNotInDraft',
656
            'Values' => [1, true],
657
        ],
658
        'byfoldererror' => [
659
            'Title' => 'Folder error',
660
            'Field' => 'ErrorParentID',
661
            'Values' => [1, true],
662
        ],
663
        'bydatabaseerror' => [
664
            'Title' => 'Error in file name',
665
            'Field' => 'ErrorInFilename',
666
            'Values' => [1, true],
667
        ],
668
        'byextensionerror' => [
669
            'Title' => 'UPPER/lower case error in file type',
670
            'Field' => 'ErrorExtensionMisMatch',
671
            'Values' => [1, true],
672
        ],
673
        'byextensionallowed' => [
674
            'Title' => 'Extension not allowed',
675
            'Field' => 'ErrorInvalidExtension',
676
            'Values' => [1, true],
677
        ],
678
        'by3to4error' => [
679
            'Title' => 'Potential SS4 migration error',
680
            'Field' => 'ErrorInSs3Ss4Comparison',
681
            'Values' => [1, true],
682
        ],
683
    ];
684
    /**
685
     * @var array<string, string>
686
     */
687
    private const DISPLAYERS = [
688
        'thumbs' => 'Thumbnails',
689
        'rawlist' => 'File List',
690
        'rawlistfull' => 'Raw Data',
691
    ];
692
}
693