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

AllFilesInfo::isRealFile()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 12
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\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
            echo "Searching for " . $pathFromAssets . ' in ' . print_r($this->databaseLookupListStaging, 1);
0 ignored issues
show
Bug introduced by
Are you sure print_r($this->databaseLookupListStaging, 1) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

211
            echo "Searching for " . $pathFromAssets . ' in ' . /** @scrutinizer ignore-type */ print_r($this->databaseLookupListStaging, 1);
Loading history...
212
            $id = $this->databaseLookupListStaging[$pathFromAssets] ?? 0;
213
        }
214
215
        return $this->dataStaging[$id] ?? [];
216
    }
217
218
    /**
219
     * get data from live database row.
220
     *
221
     * @param string $pathFromAssets - full lookup list
222
     */
223
    public function getLiveData(string $pathFromAssets, ?int $id = 0): array
224
    {
225
        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...
226
            $id = $this->databaseLookupListLive[$pathFromAssets] ?? 0;
227
        }
228
229
        return $this->dataLive[$id] ?? [];
230
    }
231
232
    /**
233
     * find a value in a field in staging
234
     * returns ID of row.
235
     *
236
     * @param mixed $value
237
     */
238
    public function findInStagingData(string $fieldName, $value): int
239
    {
240
        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

240
        return self::/** @scrutinizer ignore-call */ findInData($this->dataStaging, $fieldName, $value);
Loading history...
241
    }
242
243
    /**
244
     * find a value in a field in live
245
     * returns ID of row.
246
     *
247
     * @param mixed $value
248
     */
249
    public function findInLiveData(string $fieldName, $value): int
250
    {
251
        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

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

311
            /** @scrutinizer ignore-call */ 
312
            $this->totalFileCountRaw = self::getTotalFilesCount();
Loading history...
312
            $this->filesAsArrayList = ArrayList::create();
313
            $filterFree = true;
314
            $filterField = null;
315
            $filterValues = [];
316
            if (isset($this->filters[$this->filter])) {
317
                $filterFree = false;
318
                $filterField = $this->filters[$this->filter]['Field'];
319
                $filterValues = $this->filters[$this->filter]['Values'];
320
            }
321
322
            foreach ($rawArray as $location => $fileExists) {
323
                if ($this->isPathWithAllowedExtension($this->allowedExtensions, $location)) {
324
                    $intel = $this->getDataAboutOneFile($location, $fileExists);
325
                    if ($filterFree || in_array($intel[$filterField], $filterValues, 1)) {
326
                        ++$this->totalFileCountFiltered;
327
                        $this->totalFileSizeFiltered += $intel['PathFileSize'];
328
                        $this->filesAsArrayList->push(
329
                            ArrayData::create($intel)
330
                        );
331
                    }
332
                }
333
            }
334
        }
335
336
        return $this->filesAsArrayList;
337
    }
338
339
340
    public function getFilesAsSortedArrayList(): ArrayList
341
    {
342
        if (!isset($this->filesAsSortedArrayList)) {
343
            $sortField = $this->sorters[$this->sorter]['Sort'];
344
            $headerField = $this->sorters[$this->sorter]['Group'];
345
            $this->filesAsSortedArrayList = ArrayList::create();
346
            $this->filesAsArrayList = $this->filesAsArrayList->Sort($sortField);
347
348
            $count = 0;
349
350
            $innerArray = ArrayList::create();
351
            $prevHeader = 'nothing here....';
352
            $newHeader = '';
353
            foreach ($this->filesAsArrayList as $file) {
354
                $newHeader = $file->{$headerField};
355
                if ($newHeader !== $prevHeader) {
356
                    $this->addTofilesAsSortedArrayList(
357
                        $prevHeader, //correct! important ...
358
                        $innerArray
359
                    );
360
                    $prevHeader = $newHeader;
361
                    unset($innerArray);
362
                    $innerArray = ArrayList::create();
363
                }
364
365
                if ($count >= $this->startLimit && $count < $this->endLimit) {
366
                    $innerArray->push($file);
367
                } elseif ($count >= $this->endLimit) {
368
                    break;
369
                }
370
371
                ++$count;
372
            }
373
374
            //last one!
375
            $this->addTofilesAsSortedArrayList(
376
                $newHeader,
377
                $innerArray
378
            );
379
        }
380
381
        return $this->filesAsSortedArrayList;
382
    }
383
384
    protected function addTofilesAsSortedArrayList(string $header, ArrayList $arrayList)
385
    {
386
        if ($arrayList->exists()) {
387
            $count = $this->filesAsSortedArrayList->count();
388
            $this->filesAsSortedArrayList->push(
389
                ArrayData::create(
390
                    [
391
                        'Number' => $count,
392
                        'SubTitle' => $header,
393
                        'Items' => $arrayList,
394
                    ]
395
                )
396
            );
397
        }
398
    }
399
400
    protected function getDataAboutOneFile(string $location, ?bool $fileExists): array
401
    {
402
        return OneFileInfo::inst($location, $fileExists)
403
            ->toArray();
404
    }
405
406
    /**
407
     * @param string $path - does not have to be full path
408
     */
409
    protected function isPathWithAllowedExtension(array $allowedExtensions, string $path): bool
410
    {
411
        $count = count($allowedExtensions);
412
        if (0 === $count) {
413
            return true;
414
        }
415
416
        $extension = strtolower($this->getExtension($path));
417
        if ($extension === '') {
418
            $extension = 'n/a';
419
        }
420
421
        return in_array($extension, $allowedExtensions, true);
422
    }
423
424
    protected function registerFile($path, ?bool $inFileSystem = true)
425
    {
426
        if ($path) {
427
            if (! isset($this->listOfFiles[$path])) {
428
                $this->listOfFiles[$path] = $inFileSystem;
429
                if ($this->debug) {
430
                    echo $inFileSystem ? '✓ ' : 'x ';
431
                }
432
433
                $extension = strtolower($this->getExtension($path));
434
                $this->availableExtensions[$extension] = $extension;
435
            }
436
        }
437
    }
438
439
    protected function findIdFromFileName(array $data, string $filename): int
440
    {
441
        $id = self::findInData($data, 'FileFilename', $filename);
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

441
        /** @scrutinizer ignore-call */ 
442
        $id = self::findInData($data, 'FileFilename', $filename);
Loading history...
442
        if (! $id) {
443
            $id = self::findInData($data, 'Filename', $filename);
444
        }
445
446
        return $id;
447
    }
448
449
    /**
450
     * @param mixed $value
451
     */
452
    protected function findInData(array $data, string $fieldName, $value): int
453
    {
454
        foreach ($data as $id => $row) {
455
            if (isset($row[$fieldName])) {
456
                if ($row[$fieldName] === $value) {
457
                    return (int) $id;
458
                }
459
            }
460
        }
461
462
        return 0;
463
    }
464
465
    protected function isRealFile(string $absolutePath): bool
466
    {
467
        $listOfItemsToSearchFor = Config::inst()->get(self::class, 'not_real_file_substrings');
468
        foreach ($listOfItemsToSearchFor as $test) {
469
            if (strpos($absolutePath, $test)) {
470
                return false;
471
            }
472
        }
473
474
        $fileName = basename($absolutePath);
475
476
        return ! ('error' === substr((string) $fileName, 0, 5) && '.html' === substr((string) $fileName, -5));
477
    }
478
479
    protected function getArrayOfFilesOnDisk(): array
480
    {
481
        $finalArray = [];
482
        $arrayRaw = new RecursiveIteratorIterator(
483
            new RecursiveDirectoryIterator($this->path),
484
            RecursiveIteratorIterator::SELF_FIRST
485
        );
486
        foreach ($arrayRaw as $src) {
487
            $absolutePath = $src->getPathName();
488
            if (false === $this->isRealFile($absolutePath)) {
489
                continue;
490
            }
491
            $location = trim(str_replace(ASSETS_PATH, '', $absolutePath), '/');
492
            $finalArray[$location] = $location;
493
        }
494
495
        return $finalArray;
496
    }
497
498
    /**
499
     *
500
     * returns all the files in the database except for folders.
501
     * @return array
502
     */
503
    protected function getArrayOfFilesInDatabase(): array
504
    {
505
        $finalArray = [];
506
        foreach (['Stage', 'Live'] as $stage) {
507
            Versioned::set_stage($stage);
508
            $files = File::get();
509
            foreach ($files as $file) {
510
                $row = $file->toMap();
511
                $location = trim($file->getFilename(), '/');
512
                if (!$location) {
513
                    $location = $file->generateFilename();
514
                }
515
                if ('Stage' === $stage) {
516
                    $this->dataStaging[$row['ID']] = $row;
517
                    $this->databaseLookupListStaging[$location] = $row['ID'];
518
                } elseif ('Live' === $stage) {
519
                    $this->dataLive[$row['ID']] = $row;
520
                    $this->databaseLookupListLive[$location] = $row['ID'];
521
                } else {
522
                    user_error('Can not find stage');
523
                }
524
525
                $finalArray[$location] = $location;
526
            }
527
        }
528
        return $finalArray;
529
    }
530
531
    //#############################################
532
    // CACHE
533
    //#############################################
534
535
    protected function getCacheKey(): string
536
    {
537
        return 'allfiles';
538
    }
539
540
541
    public function getAvailableExtensions(): array
542
    {
543
        return $this->availableExtensions;
544
    }
545
546
547
    public function getTotalFileCountRaw(): int
548
    {
549
        return $this->totalFileCountRaw;
550
    }
551
552
553
    public function getTotalFileCountFiltered(): int
554
    {
555
        return $this->totalFileCountFiltered;
556
    }
557
558
559
    public function getTotalFileSizeFiltered(): int
560
    {
561
        return $this->totalFileSizeFiltered;
562
    }
563
564
    public function setAvailableExtensions(array $availableExtensions): static
565
    {
566
        $this->availableExtensions = $availableExtensions;
567
        return $this;
568
    }
569
570
    public function setFilters(array $filters): static
571
    {
572
        $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...
573
        return $this;
574
    }
575
576
    public function setSorters(array $sorters): static
577
    {
578
        $this->sorters = $sorters;
579
        return $this;
580
    }
581
582
    public function setLimit(int $limit): static
583
    {
584
        $this->limit = $limit;
585
        return $this;
586
    }
587
588
    public function setStartLimit(int $startLimit): static
589
    {
590
        $this->startLimit = $startLimit;
591
        return $this;
592
    }
593
594
    public function setEndLimit(int $endLimit): static
595
    {
596
        $this->endLimit = $endLimit;
597
        return $this;
598
    }
599
600
    public function setPageNumber(int $pageNumber): static
601
    {
602
        $this->pageNumber = $pageNumber;
603
        return $this;
604
    }
605
606
    public function setSorter(string $sorter): static
607
    {
608
        $this->sorter = $sorter;
609
        return $this;
610
    }
611
612
    public function setFilter(string $filter): static
613
    {
614
        $this->filter = $filter;
615
        return $this;
616
    }
617
618
    public function setDisplayer(string $displayer): static
619
    {
620
        $this->displayer = $displayer;
621
        return $this;
622
    }
623
624
    public function setAllowedExtensions(array $allowedExtensions): static
625
    {
626
        $this->allowedExtensions = $allowedExtensions;
627
        return $this;
628
    }
629
}
630