Passed
Push — master ( 2243e7...bf7994 )
by Nicolaas
02:34
created

AllFilesInfo   F

Complexity

Total Complexity 81

Size/Duplication

Total Lines 596
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 229
c 5
b 0
f 0
dl 0
loc 596
rs 2
wmc 81

38 Methods

Rating   Name   Duplication   Size   Complexity  
A getAnyData() 0 8 2
A existsOnStaging() 0 3 1
A __construct() 0 3 1
A inst() 0 4 1
A existsOnLive() 0 3 1
A getTotalFilesCount() 0 3 1
A getTotalFileCountFiltered() 0 3 1
A getTotalFileSizesRaw() 0 11 5
A getArrayOfFilesOnDisk() 0 17 3
A setFilters() 0 4 1
A getTotalFileCountRaw() 0 3 1
A setSorters() 0 4 1
A setLimit() 0 4 1
A setEndLimit() 0 4 1
A isRealFile() 0 12 4
A findInStagingData() 0 3 1
A setAllowedExtensions() 0 4 1
A getCacheKey() 0 3 1
B getFilesAsSortedArrayList() 0 42 7
A setSorter() 0 4 1
A isPathWithAllowedExtension() 0 13 3
A getLiveData() 0 7 2
A setPageNumber() 0 4 1
B getFilesAsArrayList() 0 31 7
B getArrayOfFilesInDatabase() 0 27 6
A getStagingData() 0 7 2
A toArray() 0 37 5
A registerFile() 0 11 5
A getAvailableExtensions() 0 3 1
A getDataAboutOneFile() 0 4 1
A getTotalFileSizeFiltered() 0 3 1
A findInData() 0 11 4
A setAvailableExtensions() 0 4 1
A findInLiveData() 0 3 1
A setDisplayer() 0 4 1
A setFilter() 0 4 1
A setStartLimit() 0 4 1
A addTofilesAsSortedArrayList() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like AllFilesInfo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AllFilesInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Sunnysideup\AssetsOverview\Files;
4
5
use FilesystemIterator;
6
use RecursiveDirectoryIterator;
7
use RecursiveIteratorIterator;
8
use SilverStripe\Assets\File;
9
use SilverStripe\Assets\Folder;
10
use SilverStripe\Core\Config\Config;
11
use SilverStripe\Core\Config\Configurable;
12
use SilverStripe\Core\Injector\Injectable;
13
use SilverStripe\Core\Injector\Injector;
14
use SilverStripe\ORM\ArrayList;
15
use SilverStripe\ORM\DB;
16
use SilverStripe\Versioned\Versioned;
17
use SilverStripe\View\ArrayData;
18
use Sunnysideup\AssetsOverview\Interfaces\FileInfo;
19
use Sunnysideup\AssetsOverview\Traits\Cacher;
20
use Sunnysideup\AssetsOverview\Traits\FilesystemRelatedTraits;
21
use Sunnysideup\Flush\FlushNow;
22
23
class AllFilesInfo implements FileInfo
24
{
25
    use FilesystemRelatedTraits;
26
    use Injectable;
27
    use Configurable;
28
    use Cacher;
29
    use FlushNow;
30
31
    public static function inst(): AllFilesInfo
32
    {
33
        return Injector::inst()
34
            ->get(AllFilesInfo::class, true, [ASSETS_PATH]);
35
    }
36
37
    private static array $not_real_file_substrings = [
38
        DIRECTORY_SEPARATOR . '_resampled',
39
        DIRECTORY_SEPARATOR . '__',
40
        DIRECTORY_SEPARATOR . '.',
41
        '__Fit',
42
        '__Pad',
43
        '__Fill',
44
        '__Focus',
45
        '__Scale',
46
        '__ResizedImage',
47
    ];
48
49
    protected $debug = false;
50
51
    /**
52
     * @var string
53
     */
54
    protected string $path = '';
55
56
    /**
57
     * @var array
58
     */
59
    protected array $dataStaging = [];
60
61
    /**
62
     * @var array
63
     */
64
    protected array $dataLive = [];
65
66
    /**
67
     * @var array
68
     */
69
    protected array $listOfFiles = [];
70
71
    /**
72
     * @var array
73
     */
74
    protected array $databaseLookupListStaging = [];
75
76
77
    /**
78
     * @var array
79
     */
80
    protected array $databaseLookupListLive = [];
81
82
83
    /**
84
     * @var ArrayList
85
     */
86
    protected ArrayList $filesAsArrayList;
87
88
    /**
89
     * @var ArrayList
90
     */
91
    protected ArrayList $filesAsSortedArrayList;
92
93
    /**
94
     * @var array
95
     */
96
    protected array $availableExtensions = [];
97
98
99
100
    /**
101
     * @var int
102
     */
103
    protected int $totalFileCountRaw = 0;
104
105
106
    /**
107
     * @var int
108
     */
109
    protected int $totalFileCountFiltered = 0;
110
111
    /**
112
     * @var int
113
     */
114
    protected int $totalFileSizeFiltered = 0;
115
116
    /**
117
     * @var int
118
     */
119
    protected int $limit = 1000;
120
121
    /**
122
     * @var int
123
     */
124
    protected int $startLimit = 0;
125
126
    /**
127
     * @var int
128
     */
129
    protected int $endLimit = 0;
130
131
    /**
132
     * @var int
133
     */
134
    protected int $pageNumber = 1;
135
136
    /**
137
     * @var string
138
     */
139
    protected string $sorter = 'byfolder';
140
    protected array $sorters = [];
141
142
    /**
143
     * @var string
144
     */
145
    protected string $filter = '';
146
147
    /**
148
     * @var string
149
     */
150
    protected array $filters = [];
151
152
    /**
153
     * @var string
154
     */
155
    protected string $displayer = 'thumbs';
156
157
    /**
158
     * @var array
159
     */
160
    protected array $allowedExtensions = [];
161
162
163
    public function __construct($path)
164
    {
165
        $this->path = $path;
166
    }
167
168
    public function getTotalFilesCount(): int
169
    {
170
        return (int) count($this->listOfFiles);
171
    }
172
173
    /**
174
     * does the file exists in the database on staging?
175
     */
176
    public function existsOnStaging(int $id): bool
177
    {
178
        return isset($this->dataStaging[$id]);
179
    }
180
181
    /**
182
     * does the file exists in the database on live?
183
     */
184
    public function existsOnLive(int $id): bool
185
    {
186
        return isset($this->dataLive[$id]);
187
    }
188
189
    /**
190
     * get data from staging database row.
191
     */
192
    public function getAnyData(string $pathFromAssets, ?int $id = 0): array
193
    {
194
        $data = self::getStagingData($pathFromAssets, $id);
0 ignored issues
show
Bug Best Practice introduced by
The method Sunnysideup\AssetsOvervi...sInfo::getStagingData() is not static, but was called statically. ( Ignorable by Annotation )

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

194
        /** @scrutinizer ignore-call */ 
195
        $data = self::getStagingData($pathFromAssets, $id);
Loading history...
195
        if (empty($data)) {
196
            $data = self::getLiveData($pathFromAssets, $id);
0 ignored issues
show
Bug Best Practice introduced by
The method Sunnysideup\AssetsOvervi...ilesInfo::getLiveData() is not static, but was called statically. ( Ignorable by Annotation )

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

196
            /** @scrutinizer ignore-call */ 
197
            $data = self::getLiveData($pathFromAssets, $id);
Loading history...
197
        }
198
199
        return $data;
200
    }
201
202
    /**
203
     * get data from staging database row.
204
     *
205
     * @param string $pathFromAssets from the root of assets
206
     * @param int    $id
207
     */
208
    public function getStagingData(string $pathFromAssets, ?int $id = 0): array
209
    {
210
        if (! $id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
211
            $id = $this->databaseLookupListStaging[$pathFromAssets] ?? 0;
212
        }
213
214
        return $this->dataStaging[$id] ?? [];
215
    }
216
217
    /**
218
     * get data from live database row.
219
     *
220
     * @param string $pathFromAssets - full lookup list
221
     */
222
    public function getLiveData(string $pathFromAssets, ?int $id = 0): array
223
    {
224
        if (! $id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
225
            $id = $this->databaseLookupListLive[$pathFromAssets] ?? 0;
226
        }
227
228
        return $this->dataLive[$id] ?? [];
229
    }
230
231
    /**
232
     * find a value in a field in staging
233
     * returns ID of row.
234
     *
235
     * @param mixed $value
236
     */
237
    public function findInStagingData(string $fieldName, $value): int
238
    {
239
        return self::findInData($this->dataStaging, $fieldName, $value);
0 ignored issues
show
Bug Best Practice introduced by
The method Sunnysideup\AssetsOvervi...FilesInfo::findInData() is not static, but was called statically. ( Ignorable by Annotation )

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

239
        return self::/** @scrutinizer ignore-call */ findInData($this->dataStaging, $fieldName, $value);
Loading history...
240
    }
241
242
    /**
243
     * find a value in a field in live
244
     * returns ID of row.
245
     *
246
     * @param mixed $value
247
     */
248
    public function findInLiveData(string $fieldName, $value): int
249
    {
250
        return self::findInData($this->dataLive, $fieldName, $value);
0 ignored issues
show
Bug Best Practice introduced by
The method Sunnysideup\AssetsOvervi...FilesInfo::findInData() is not static, but was called statically. ( Ignorable by Annotation )

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

250
        return self::/** @scrutinizer ignore-call */ findInData($this->dataLive, $fieldName, $value);
Loading history...
251
    }
252
253
    public function getTotalFileSizesRaw()
254
    {
255
        $bytestotal = 0;
256
        $absoluteAssetPath = realpath(ASSETS_PATH);
257
        if (false !== $absoluteAssetPath && '' !== $absoluteAssetPath && file_exists($absoluteAssetPath)) {
258
            foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($absoluteAssetPath, FilesystemIterator::SKIP_DOTS)) as $object) {
259
                $bytestotal += $object->getSize();
260
            }
261
        }
262
263
        return $bytestotal;
264
    }
265
266
    public function toArray(): array
267
    {
268
        if ([] === $this->listOfFiles) {
269
            $cachekey = $this->getCacheKey();
270
            if ($this->hasCacheKey($cachekey)) {
271
                $this->listOfFiles = $this->getCacheValue($cachekey);
272
                $this->availableExtensions = $this->getCacheValue($cachekey . 'availableExtensions');
273
                $this->dataStaging = $this->getCacheValue($cachekey . 'dataStaging');
274
                $this->dataLive = $this->getCacheValue($cachekey . 'dataLive');
275
                $this->databaseLookupListStaging = $this->getCacheValue($cachekey . 'databaseLookupStaging');
276
                $this->databaseLookupListLive = $this->getCacheValue($cachekey . 'databaseLookupLive');
277
            } else {
278
                //disk
279
                $diskArray = $this->getArrayOfFilesOnDisk();
280
                foreach ($diskArray as $path) {
281
                    $this->registerFile($path, true);
282
                }
283
284
                //database
285
                $databaseArray = $this->getArrayOfFilesInDatabase();
286
                foreach ($databaseArray as $path) {
287
                    $location = trim(str_replace(ASSETS_PATH, '', $path), '/');
288
                    $this->registerFile($location, false);
289
                }
290
291
                asort($this->listOfFiles);
292
                asort($this->availableExtensions);
293
                $this->setCacheValue($cachekey, $this->listOfFiles);
294
                $this->setCacheValue($cachekey . 'availableExtensions', $this->availableExtensions);
295
                $this->setCacheValue($cachekey . 'dataStaging', $this->dataStaging);
296
                $this->setCacheValue($cachekey . 'dataLive', $this->dataLive);
297
                $this->setCacheValue($cachekey . 'databaseLookupStaging', $this->databaseLookupListStaging);
298
                $this->setCacheValue($cachekey . 'databaseLookupLive', $this->databaseLookupListLive);
299
            }
300
        }
301
302
        return $this->listOfFiles;
303
    }
304
305
    public function getFilesAsArrayList(): ArrayList
306
    {
307
        if (!isset($this->filesAsArrayList)) {
308
            $rawArray = $this->toArray();
309
            //prepare loop
310
            $this->totalFileCountRaw = self::getTotalFilesCount();
0 ignored issues
show
Bug Best Practice introduced by
The method Sunnysideup\AssetsOvervi...o::getTotalFilesCount() is not static, but was called statically. ( Ignorable by Annotation )

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

310
            /** @scrutinizer ignore-call */ 
311
            $this->totalFileCountRaw = self::getTotalFilesCount();
Loading history...
311
            $this->filesAsArrayList = ArrayList::create();
312
            $filterFree = true;
313
            $filterField = null;
314
            $filterValues = [];
315
            if (isset($this->filters[$this->filter])) {
316
                $filterFree = false;
317
                $filterField = $this->filters[$this->filter]['Field'];
318
                $filterValues = $this->filters[$this->filter]['Values'];
319
            }
320
321
            foreach ($rawArray as $location => $fileExists) {
322
                if ($this->isPathWithAllowedExtension($this->allowedExtensions, $location)) {
323
                    $intel = $this->getDataAboutOneFile($location, $fileExists);
324
                    if ($filterFree || in_array($intel[$filterField], $filterValues, 1)) {
325
                        ++$this->totalFileCountFiltered;
326
                        $this->totalFileSizeFiltered += $intel['PathFileSize'];
327
                        $this->filesAsArrayList->push(
328
                            ArrayData::create($intel)
329
                        );
330
                    }
331
                }
332
            }
333
        }
334
335
        return $this->filesAsArrayList;
336
    }
337
338
339
    public function getFilesAsSortedArrayList(): ArrayList
340
    {
341
        if (!isset($this->filesAsSortedArrayList)) {
342
            $sortField = $this->sorters[$this->sorter]['Sort'];
343
            $headerField = $this->sorters[$this->sorter]['Group'];
344
            $this->filesAsSortedArrayList = ArrayList::create();
345
            $this->filesAsArrayList = $this->filesAsArrayList->Sort($sortField);
346
347
            $count = 0;
348
349
            $innerArray = ArrayList::create();
350
            $prevHeader = 'nothing here....';
351
            $newHeader = '';
352
            foreach ($this->filesAsArrayList as $file) {
353
                $newHeader = $file->{$headerField};
354
                if ($newHeader !== $prevHeader) {
355
                    $this->addTofilesAsSortedArrayList(
356
                        $prevHeader, //correct! important ...
357
                        $innerArray
358
                    );
359
                    $prevHeader = $newHeader;
360
                    unset($innerArray);
361
                    $innerArray = ArrayList::create();
362
                }
363
364
                if ($count >= $this->startLimit && $count < $this->endLimit) {
365
                    $innerArray->push($file);
366
                } elseif ($count >= $this->endLimit) {
367
                    break;
368
                }
369
370
                ++$count;
371
            }
372
373
            //last one!
374
            $this->addTofilesAsSortedArrayList(
375
                $newHeader,
376
                $innerArray
377
            );
378
        }
379
380
        return $this->filesAsSortedArrayList;
381
    }
382
383
    protected function addTofilesAsSortedArrayList(string $header, ArrayList $arrayList)
384
    {
385
        if ($arrayList->exists()) {
386
            $count = $this->filesAsSortedArrayList->count();
387
            $this->filesAsSortedArrayList->push(
388
                ArrayData::create(
389
                    [
390
                        'Number' => $count,
391
                        'SubTitle' => $header,
392
                        'Items' => $arrayList,
393
                    ]
394
                )
395
            );
396
        }
397
    }
398
399
    protected function getDataAboutOneFile(string $location, ?bool $fileExists): array
400
    {
401
        return OneFileInfo::inst($location, $fileExists)
402
            ->toArray();
403
    }
404
405
    /**
406
     * @param string $path - does not have to be full path
407
     */
408
    protected function isPathWithAllowedExtension(array $allowedExtensions, string $path): bool
409
    {
410
        $count = count($allowedExtensions);
411
        if (0 === $count) {
412
            return true;
413
        }
414
415
        $extension = strtolower($this->getExtension($path));
416
        if ($extension === '') {
417
            $extension = 'n/a';
418
        }
419
420
        return in_array($extension, $allowedExtensions, true);
421
    }
422
423
    protected function registerFile($path, ?bool $inFileSystem = true)
424
    {
425
        if ($path) {
426
            if (! isset($this->listOfFiles[$path])) {
427
                $this->listOfFiles[$path] = $inFileSystem;
428
                if ($this->debug) {
429
                    echo $inFileSystem ? '✓ ' : 'x ';
430
                }
431
432
                $extension = strtolower($this->getExtension($path));
433
                $this->availableExtensions[$extension] = $extension;
434
            }
435
        }
436
    }
437
438
439
    /**
440
     * @param mixed $value
441
     */
442
    protected function findInData(array $data, string $fieldName, $value): int
443
    {
444
        foreach ($data as $id => $row) {
445
            if (isset($row[$fieldName])) {
446
                if ($row[$fieldName] === $value) {
447
                    return (int) $id;
448
                }
449
            }
450
        }
451
452
        return 0;
453
    }
454
455
    protected function isRealFile(string $absolutePath): bool
456
    {
457
        $listOfItemsToSearchFor = Config::inst()->get(self::class, 'not_real_file_substrings');
458
        foreach ($listOfItemsToSearchFor as $test) {
459
            if (strpos($absolutePath, $test)) {
460
                return false;
461
            }
462
        }
463
464
        $fileName = basename($absolutePath);
465
        $isErrorPage = ('error' === substr((string) $fileName, 0, 5) && '.html' === substr((string) $fileName, -5));
466
        return ! $isErrorPage;
467
    }
468
469
    protected function getArrayOfFilesOnDisk(): array
470
    {
471
        $finalArray = [];
472
        $arrayRaw = new RecursiveIteratorIterator(
473
            new RecursiveDirectoryIterator($this->path),
474
            RecursiveIteratorIterator::SELF_FIRST
475
        );
476
        foreach ($arrayRaw as $src) {
477
            $absolutePath = $src->getPathName();
478
            if (false === $this->isRealFile($absolutePath)) {
479
                continue;
480
            }
481
            $location = trim(str_replace(ASSETS_PATH, '', $absolutePath), '/');
482
            $finalArray[$location] = $location;
483
        }
484
485
        return $finalArray;
486
    }
487
488
    /**
489
     *
490
     * returns all the files in the database except for folders.
491
     * @return array
492
     */
493
    protected function getArrayOfFilesInDatabase(): array
494
    {
495
        $finalArray = [];
496
        foreach (['Stage', 'Live'] as $stage) {
497
            Versioned::set_stage($stage);
498
            $files = File::get();
499
            foreach ($files as $file) {
500
                $row = $file->toMap();
501
                $location = trim($file->getFilename(), '/');
502
                if (!$location) {
503
                    $location = $file->generateFilename();
504
                }
505
                if ('Stage' === $stage) {
506
                    $this->dataStaging[$row['ID']] = $row;
507
                    $this->databaseLookupListStaging[$location] = $row['ID'];
508
                } elseif ('Live' === $stage) {
509
                    $this->dataLive[$row['ID']] = $row;
510
                    $this->databaseLookupListLive[$location] = $row['ID'];
511
                } else {
512
                    user_error('Can not find stage');
513
                }
514
515
                $finalArray[$location] = $location;
516
            }
517
        }
518
        Versioned::set_stage('Stage');
519
        return $finalArray;
520
    }
521
522
    //#############################################
523
    // CACHE
524
    //#############################################
525
526
    protected function getCacheKey(): string
527
    {
528
        return 'allfiles';
529
    }
530
531
532
    public function getAvailableExtensions(): array
533
    {
534
        return $this->availableExtensions;
535
    }
536
537
538
    public function getTotalFileCountRaw(): int
539
    {
540
        return $this->totalFileCountRaw;
541
    }
542
543
544
    public function getTotalFileCountFiltered(): int
545
    {
546
        return $this->totalFileCountFiltered;
547
    }
548
549
550
    public function getTotalFileSizeFiltered(): int
551
    {
552
        return $this->totalFileSizeFiltered;
553
    }
554
555
    public function setAvailableExtensions(array $availableExtensions): static
556
    {
557
        $this->availableExtensions = $availableExtensions;
558
        return $this;
559
    }
560
561
    public function setFilters(array $filters): static
562
    {
563
        $this->filters = $filters;
0 ignored issues
show
Documentation Bug introduced by
It seems like $filters of type array is incompatible with the declared type string of property $filters.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
564
        return $this;
565
    }
566
567
    public function setSorters(array $sorters): static
568
    {
569
        $this->sorters = $sorters;
570
        return $this;
571
    }
572
573
    public function setLimit(int $limit): static
574
    {
575
        $this->limit = $limit;
576
        return $this;
577
    }
578
579
    public function setStartLimit(int $startLimit): static
580
    {
581
        $this->startLimit = $startLimit;
582
        return $this;
583
    }
584
585
    public function setEndLimit(int $endLimit): static
586
    {
587
        $this->endLimit = $endLimit;
588
        return $this;
589
    }
590
591
    public function setPageNumber(int $pageNumber): static
592
    {
593
        $this->pageNumber = $pageNumber;
594
        return $this;
595
    }
596
597
    public function setSorter(string $sorter): static
598
    {
599
        $this->sorter = $sorter;
600
        return $this;
601
    }
602
603
    public function setFilter(string $filter): static
604
    {
605
        $this->filter = $filter;
606
        return $this;
607
    }
608
609
    public function setDisplayer(string $displayer): static
610
    {
611
        $this->displayer = $displayer;
612
        return $this;
613
    }
614
615
    public function setAllowedExtensions(array $allowedExtensions): static
616
    {
617
        $this->allowedExtensions = $allowedExtensions;
618
        return $this;
619
    }
620
}
621