Passed
Push — master ( 5e2021...3d7c5f )
by Nicolaas
02:55
created

AllFilesInfo::setSorter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
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\Versioned\Versioned;
16
use SilverStripe\View\ArrayData;
17
use Sunnysideup\AssetsOverview\Interfaces\FileInfo;
18
use Sunnysideup\AssetsOverview\Traits\Cacher;
19
use Sunnysideup\AssetsOverview\Traits\FilesystemRelatedTraits;
20
use Sunnysideup\Flush\FlushNow;
21
22
class AllFilesInfo implements FileInfo
23
{
24
    use FilesystemRelatedTraits;
25
    use Injectable;
26
    use Configurable;
27
    use Cacher;
28
    use FlushNow;
29
30
    public static function inst(): AllFilesInfo
31
    {
32
        return Injector::inst()
33
            ->get(AllFilesInfo::class, true, [ASSETS_PATH]);
34
    }
35
36
    public function setVerbose(bool $b): static
37
    {
38
        $this->verbose = $b;
39
        return $this;
40
    }
41
42
    public function setNoCache(bool $b): static
43
    {
44
        $this->noCache = $b;
45
        return $this;
46
    }
47
48
    private static array $not_real_file_substrings = [
49
        DIRECTORY_SEPARATOR . '_resampled',
50
        DIRECTORY_SEPARATOR . '__',
51
        DIRECTORY_SEPARATOR . '.',
52
        '__Fit',
53
        '__Pad',
54
        '__Fill',
55
        '__Focus',
56
        '__Scale',
57
        '__ResizedImage',
58
    ];
59
60
    protected bool $noCache = false;
61
    protected bool $verbose = false;
62
63
    /**
64
     * @var string
65
     */
66
    protected string $path = '';
67
68
    /**
69
     * @var array
70
     */
71
    protected array $dataStaging = [];
72
73
    /**
74
     * @var array
75
     */
76
    protected array $dataLive = [];
77
78
    /**
79
     * @var array
80
     */
81
    protected array $listOfFiles = [];
82
83
    /**
84
     * @var array
85
     */
86
    protected array $databaseLookupListStaging = [];
87
88
89
    /**
90
     * @var array
91
     */
92
    protected array $databaseLookupListLive = [];
93
94
95
    /**
96
     * @var ArrayList
97
     */
98
    protected ArrayList $filesAsArrayList;
99
100
    /**
101
     * @var ArrayList
102
     */
103
    protected ArrayList $filesAsSortedArrayList;
104
105
    /**
106
     * @var array
107
     */
108
    protected array $availableExtensions = [];
109
110
111
112
    /**
113
     * @var int
114
     */
115
    protected int $totalFileCountRaw = 0;
116
117
118
    /**
119
     * @var int
120
     */
121
    protected int $totalFileCountFiltered = 0;
122
123
    /**
124
     * @var int
125
     */
126
    protected int $totalFileSizeFiltered = 0;
127
128
    /**
129
     * @var int
130
     */
131
    protected int $limit = 1000;
132
133
    /**
134
     * @var int
135
     */
136
    protected int $startLimit = 0;
137
138
    /**
139
     * @var int
140
     */
141
    protected int $endLimit = 0;
142
143
    /**
144
     * @var int
145
     */
146
    protected int $pageNumber = 1;
147
148
    /**
149
     * @var string
150
     */
151
    protected string $sorter = 'byfolder';
152
    protected array $sorters = [];
153
154
    /**
155
     * @var string
156
     */
157
    protected string $filter = '';
158
159
    /**
160
     * @var string
161
     */
162
    protected array $filters = [];
163
164
    /**
165
     * @var string
166
     */
167
    protected string $displayer = 'thumbs';
168
169
    /**
170
     * @var array
171
     */
172
    protected array $allowedExtensions = [];
173
174
175
    public function __construct(?string $path = ASSETS_PATH)
176
    {
177
        $this->path = $path;
178
    }
179
180
    public function getTotalFilesCount(): int
181
    {
182
        return (int) count($this->listOfFiles);
183
    }
184
185
    /**
186
     * does the file exists in the database on staging?
187
     */
188
    public function existsOnStaging(int $id): bool
189
    {
190
        return isset($this->dataStaging[$id]);
191
    }
192
193
    /**
194
     * does the file exists in the database on live?
195
     */
196
    public function existsOnLive(int $id): bool
197
    {
198
        return isset($this->dataLive[$id]);
199
    }
200
201
    /**
202
     * get data from staging database row.
203
     */
204
    public function getAnyData(string $pathFromAssets, ?int $id = 0): array
205
    {
206
        $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

206
        /** @scrutinizer ignore-call */ 
207
        $data = self::getStagingData($pathFromAssets, $id);
Loading history...
207
        if (empty($data)) {
208
            $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

208
            /** @scrutinizer ignore-call */ 
209
            $data = self::getLiveData($pathFromAssets, $id);
Loading history...
209
        }
210
211
        return $data;
212
    }
213
214
    /**
215
     * get data from staging database row.
216
     *
217
     * @param string $pathFromAssets from the root of assets
218
     * @param int    $id
219
     */
220
    public function getStagingData(string $pathFromAssets, ?int $id = 0): array
221
    {
222
        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...
223
            $id = $this->databaseLookupListStaging[$pathFromAssets] ?? 0;
224
        }
225
226
        return $this->dataStaging[$id] ?? [];
227
    }
228
229
    /**
230
     * get data from live database row.
231
     *
232
     * @param string $pathFromAssets - full lookup list
233
     */
234
    public function getLiveData(string $pathFromAssets, ?int $id = 0): array
235
    {
236
        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...
237
            $id = $this->databaseLookupListLive[$pathFromAssets] ?? 0;
238
        }
239
240
        return $this->dataLive[$id] ?? [];
241
    }
242
243
    /**
244
     * find a value in a field in staging
245
     * returns ID of row.
246
     *
247
     * @param mixed $value
248
     */
249
    public function findInStagingData(string $fieldName, $value): int
250
    {
251
        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

251
        return self::/** @scrutinizer ignore-call */ findInData($this->dataStaging, $fieldName, $value);
Loading history...
252
    }
253
254
    /**
255
     * find a value in a field in live
256
     * returns ID of row.
257
     *
258
     * @param mixed $value
259
     */
260
    public function findInLiveData(string $fieldName, $value): int
261
    {
262
        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

262
        return self::/** @scrutinizer ignore-call */ findInData($this->dataLive, $fieldName, $value);
Loading history...
263
    }
264
265
    public function getTotalFileSizesRaw()
266
    {
267
        $bytestotal = 0;
268
        $absoluteAssetPath = realpath(ASSETS_PATH);
269
        if (false !== $absoluteAssetPath && '' !== $absoluteAssetPath && file_exists($absoluteAssetPath)) {
270
            foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($absoluteAssetPath, FilesystemIterator::SKIP_DOTS)) as $object) {
271
                $bytestotal += $object->getSize();
272
            }
273
        }
274
275
        return $bytestotal;
276
    }
277
278
    public function toArray(): array
279
    {
280
        if ([] === $this->listOfFiles) {
281
            $cachekey = $this->getCacheKey();
282
            if ($this->hasCacheKey($cachekey) && $this->noCache === false) {
283
                $this->listOfFiles = $this->getCacheValue($cachekey);
284
                $this->availableExtensions = $this->getCacheValue($cachekey . 'availableExtensions');
285
                $this->dataStaging = $this->getCacheValue($cachekey . 'dataStaging');
286
                $this->dataLive = $this->getCacheValue($cachekey . 'dataLive');
287
                $this->databaseLookupListStaging = $this->getCacheValue($cachekey . 'databaseLookupStaging');
288
                $this->databaseLookupListLive = $this->getCacheValue($cachekey . 'databaseLookupLive');
289
            } else {
290
                //disk
291
                $diskArray = $this->getArrayOfFilesOnDisk();
292
                foreach ($diskArray as $path) {
293
                    $this->registerFile($path, true);
294
                }
295
296
                //database
297
                $databaseArray = $this->getArrayOfFilesInDatabase();
298
                foreach ($databaseArray as $path) {
299
                    $location = trim(str_replace(ASSETS_PATH, '', $path), '/');
300
                    $this->registerFile($location, false);
301
                }
302
303
                asort($this->listOfFiles);
304
                asort($this->availableExtensions);
305
                $this->setCacheValue($cachekey, $this->listOfFiles);
306
                $this->setCacheValue($cachekey . 'availableExtensions', $this->availableExtensions);
307
                $this->setCacheValue($cachekey . 'dataStaging', $this->dataStaging);
308
                $this->setCacheValue($cachekey . 'dataLive', $this->dataLive);
309
                $this->setCacheValue($cachekey . 'databaseLookupStaging', $this->databaseLookupListStaging);
310
                $this->setCacheValue($cachekey . 'databaseLookupLive', $this->databaseLookupListLive);
311
            }
312
        }
313
314
        return $this->listOfFiles;
315
    }
316
317
    public function getFilesAsArrayList(): ArrayList
318
    {
319
        if (!isset($this->filesAsArrayList)) {
320
            $rawArray = $this->toArray();
321
            //prepare loop
322
            $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

322
            /** @scrutinizer ignore-call */ 
323
            $this->totalFileCountRaw = self::getTotalFilesCount();
Loading history...
323
            $this->filesAsArrayList = ArrayList::create();
324
            $filterFree = true;
325
            $filterField = null;
326
            $filterValues = [];
327
            if (isset($this->filters[$this->filter])) {
328
                $filterFree = false;
329
                $filterField = $this->filters[$this->filter]['Field'];
330
                $filterValues = $this->filters[$this->filter]['Values'];
331
            }
332
333
            foreach ($rawArray as $location => $fileExists) {
334
                if ($this->isPathWithAllowedExtension($this->allowedExtensions, $location)) {
335
                    $intel = $this->getDataAboutOneFile($location, $fileExists);
336
                    if ($filterFree || in_array($intel[$filterField], $filterValues, 1)) {
337
                        ++$this->totalFileCountFiltered;
338
                        $this->totalFileSizeFiltered += $intel['PathFileSize'];
339
                        $this->filesAsArrayList->push(
340
                            ArrayData::create($intel)
341
                        );
342
                    }
343
                }
344
            }
345
        }
346
347
        return $this->filesAsArrayList;
348
    }
349
350
351
    public function getFilesAsSortedArrayList(): ArrayList
352
    {
353
        if (!isset($this->filesAsSortedArrayList)) {
354
            $sortField = $this->sorters[$this->sorter]['Sort'];
355
            $headerField = $this->sorters[$this->sorter]['Group'];
356
            $this->filesAsSortedArrayList = ArrayList::create();
357
            $this->filesAsArrayList = $this->filesAsArrayList->Sort($sortField);
358
359
            $count = 0;
360
361
            $innerArray = ArrayList::create();
362
            $prevHeader = 'nothing here....';
363
            $newHeader = '';
364
            foreach ($this->filesAsArrayList as $file) {
365
                $newHeader = $file->{$headerField};
366
                if ($newHeader !== $prevHeader) {
367
                    $this->addTofilesAsSortedArrayList(
368
                        $prevHeader, //correct! important ...
369
                        $innerArray
370
                    );
371
                    $prevHeader = $newHeader;
372
                    unset($innerArray);
373
                    $innerArray = ArrayList::create();
374
                }
375
376
                if ($count >= $this->startLimit && $count < $this->endLimit) {
377
                    $innerArray->push($file);
378
                } elseif ($count >= $this->endLimit) {
379
                    break;
380
                }
381
382
                ++$count;
383
            }
384
385
            //last one!
386
            $this->addTofilesAsSortedArrayList(
387
                $newHeader,
388
                $innerArray
389
            );
390
        }
391
392
        return $this->filesAsSortedArrayList;
393
    }
394
395
396
    public function getAvailableExtensions(): array
397
    {
398
        return $this->availableExtensions;
399
    }
400
401
402
    public function getTotalFileCountRaw(): int
403
    {
404
        return $this->totalFileCountRaw;
405
    }
406
407
408
    public function getTotalFileCountFiltered(): int
409
    {
410
        return $this->totalFileCountFiltered;
411
    }
412
413
414
    public function getTotalFileSizeFiltered(): int
415
    {
416
        return $this->totalFileSizeFiltered;
417
    }
418
419
    public function setAvailableExtensions(array $availableExtensions): static
420
    {
421
        $this->availableExtensions = $availableExtensions;
422
        return $this;
423
    }
424
425
    public function setFilters(array $filters): static
426
    {
427
        $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...
428
        return $this;
429
    }
430
431
    public function setSorters(array $sorters): static
432
    {
433
        $this->sorters = $sorters;
434
        return $this;
435
    }
436
437
    public function setLimit(int $limit): static
438
    {
439
        $this->limit = $limit;
440
        return $this;
441
    }
442
443
    public function setStartLimit(int $startLimit): static
444
    {
445
        $this->startLimit = $startLimit;
446
        return $this;
447
    }
448
449
    public function setEndLimit(int $endLimit): static
450
    {
451
        $this->endLimit = $endLimit;
452
        return $this;
453
    }
454
455
    public function setPageNumber(int $pageNumber): static
456
    {
457
        $this->pageNumber = $pageNumber;
458
        return $this;
459
    }
460
461
    public function setSorter(string $sorter): static
462
    {
463
        $this->sorter = $sorter;
464
        return $this;
465
    }
466
467
    public function setFilter(string $filter): static
468
    {
469
        $this->filter = $filter;
470
        return $this;
471
    }
472
473
    public function setDisplayer(string $displayer): static
474
    {
475
        $this->displayer = $displayer;
476
        return $this;
477
    }
478
479
    public function setAllowedExtensions(array $allowedExtensions): static
480
    {
481
        $this->allowedExtensions = $allowedExtensions;
482
        return $this;
483
    }
484
485
486
    protected function addTofilesAsSortedArrayList(string $header, ArrayList $arrayList)
487
    {
488
        if ($arrayList->exists()) {
489
            $count = $this->filesAsSortedArrayList->count();
490
            $this->filesAsSortedArrayList->push(
491
                ArrayData::create(
492
                    [
493
                        'Number' => $count,
494
                        'SubTitle' => $header,
495
                        'Items' => $arrayList,
496
                    ]
497
                )
498
            );
499
        }
500
    }
501
502
    protected function getDataAboutOneFile(string $location, ?bool $fileExists): array
0 ignored issues
show
Unused Code introduced by
The parameter $fileExists is not used and could be removed. ( Ignorable by Annotation )

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

502
    protected function getDataAboutOneFile(string $location, /** @scrutinizer ignore-unused */ ?bool $fileExists): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
503
    {
504
        $obj = OneFileInfo::inst($location);
505
        $obj->setNoCache($this->noCache);
506
        return $obj->toArray();
507
    }
508
509
    /**
510
     * @param string $path - does not have to be full path
511
     */
512
    protected function isPathWithAllowedExtension(array $allowedExtensions, string $path): bool
513
    {
514
        $count = count($allowedExtensions);
515
        if (0 === $count) {
516
            return true;
517
        }
518
519
        $extension = strtolower($this->getExtension($path));
520
        if ($extension === '') {
521
            $extension = 'n/a';
522
        }
523
524
        return in_array($extension, $allowedExtensions, true);
525
    }
526
527
    protected function registerFile($path, ?bool $inFileSystem = true)
528
    {
529
        if ($path) {
530
            if (! isset($this->listOfFiles[$path])) {
531
                $this->listOfFiles[$path] = $inFileSystem;
532
                if ($this->verbose) {
533
                    if ($inFileSystem) {
534
                        echo '✓ ';
535
                    } else {
536
                        echo PHP_EOL . 'x NOT IN FILE SYSTEM: ' . $path . ' ' . PHP_EOL;
537
                    }
538
                }
539
540
                $extension = strtolower($this->getExtension($path));
541
                $this->availableExtensions[$extension] = $extension;
542
            }
543
        }
544
    }
545
546
547
    /**
548
     * @param mixed $value
549
     */
550
    protected function findInData(array $data, string $fieldName, $value): int
551
    {
552
        foreach ($data as $id => $row) {
553
            if (isset($row[$fieldName])) {
554
                if ($row[$fieldName] === $value) {
555
                    return (int) $id;
556
                }
557
            }
558
        }
559
560
        return 0;
561
    }
562
563
    protected function isRealFile(string $absolutePath): bool
564
    {
565
        $listOfItemsToSearchFor = Config::inst()->get(self::class, 'not_real_file_substrings');
566
        foreach ($listOfItemsToSearchFor as $test) {
567
            if (strpos($absolutePath, $test)) {
568
                return false;
569
            }
570
        }
571
572
        $fileName = basename($absolutePath);
573
        $isErrorPage = ('error' === substr((string) $fileName, 0, 5) && '.html' === substr((string) $fileName, -5));
574
        return ! $isErrorPage;
575
    }
576
577
    protected function getArrayOfFilesOnDisk(): array
578
    {
579
        $finalArray = [];
580
        $arrayRaw = new RecursiveIteratorIterator(
581
            new RecursiveDirectoryIterator($this->path),
582
            RecursiveIteratorIterator::SELF_FIRST
583
        );
584
        foreach ($arrayRaw as $src) {
585
            $absolutePath = $src->getPathName();
586
            if (false === $this->isRealFile($absolutePath)) {
587
                continue;
588
            }
589
            if ($src->isDir()) {
590
                continue;
591
            }
592
            $location = trim(str_replace(ASSETS_PATH, '', $absolutePath), '/');
593
            $finalArray[$location] = $location;
594
        }
595
596
        return $finalArray;
597
    }
598
599
    /**
600
     *
601
     * returns all the files in the database except for folders.
602
     * @return array
603
     */
604
    protected function getArrayOfFilesInDatabase(): array
605
    {
606
        $finalArray = [];
607
        foreach (['Stage', 'Live'] as $stage) {
608
            Versioned::set_stage($stage);
609
            $files = File::get()->exclude(['ClassName' => Folder::class]);
610
            foreach ($files as $file) {
611
                $row = $file->toMap();
612
                $location = trim($file->getFilename(), '/');
613
                if (!$location) {
614
                    $location = $file->generateFilename();
615
                }
616
                if ('Stage' === $stage) {
617
                    $this->dataStaging[$row['ID']] = $row;
618
                    $this->databaseLookupListStaging[$location] = $row['ID'];
619
                } elseif ('Live' === $stage) {
620
                    $this->dataLive[$row['ID']] = $row;
621
                    $this->databaseLookupListLive[$location] = $row['ID'];
622
                } else {
623
                    user_error('Can not find stage');
624
                }
625
626
                $finalArray[$location] = $location;
627
            }
628
        }
629
        Versioned::set_stage('Stage');
630
        return $finalArray;
631
    }
632
633
    //#############################################
634
    // CACHE
635
    //#############################################
636
637
    protected function getCacheKey(): string
638
    {
639
        return 'allfiles';
640
    }
641
}
642