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

OneFileInfo::setNoCache()   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 Exception;
6
use SilverStripe\Assets\File;
7
use SilverStripe\Assets\Folder;
8
use SilverStripe\Assets\Storage\DBFile;
9
use SilverStripe\Control\Controller;
10
use SilverStripe\Core\Config\Configurable;
11
use SilverStripe\Core\Injector\Injectable;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\ORM\DataObject;
14
use SilverStripe\ORM\DB;
15
use SilverStripe\ORM\FieldType\DBDate;
16
use SilverStripe\ORM\FieldType\DBDatetime;
17
use SilverStripe\ORM\ValidationResult;
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 OneFileInfo implements FileInfo
24
{
25
    use FilesystemRelatedTraits;
26
    use Injectable;
27
    use Configurable;
28
    use Cacher;
29
    use FlushNow;
30
31
    protected static array $cached_inst = [];
32
33
    public static function inst(string $path): OneFileInfo
34
    {
35
        if (!isset(self::$cached_inst[$path])) {
36
            self::$cached_inst[$path] = new OneFileInfo($path);
37
        }
38
        return self::$cached_inst[$path];
39
    }
40
41
    protected bool $debug = false;
42
    protected bool $noCache = false;
43
44
    public function setDebug(bool $b): static
45
    {
46
        $this->debug = $b;
47
        return $this;
48
    }
49
50
    public function setNoCache(bool $b): static
51
    {
52
        $this->noCache = $b;
53
        return $this;
54
    }
55
56
    protected array $errorFields = [
57
        'ErrorDBNotPresent',
58
        'ErrorDBNotPresentLive',
59
        'ErrorDBNotPresentStaging',
60
        'ErrorExtensionMisMatch',
61
        'ErrorFindingFolder',
62
        'ErrorInFilename',
63
        'ErrorInSs3Ss4Comparison',
64
        'ErrorParentID',
65
        'ErrorInDraftOnly',
66
        'ErrorNotInDraft',
67
    ];
68
69
    /**
70
     * @var string
71
     */
72
    protected string $pathHash = '';
73
74
    /**
75
     * @var string
76
     */
77
    protected string $path = '';
78
    protected string $absolutePath = '';
79
80
    /**
81
     * @var array
82
     */
83
    protected array $intel = [];
84
85
    /**
86
     * @var array
87
     */
88
    protected array $parthParts = [];
89
90
    /**
91
     * @var bool
92
     */
93
    protected bool $physicalFileExists = false;
94
95
    protected array $folderCache = [];
96
97
    public function __construct(string $location)
98
    {
99
        $this->path = $location;
100
        $this->absolutePath = ASSETS_PATH . DIRECTORY_SEPARATOR . $this->path;
101
        $this->pathHash = md5($this->path);
102
        $this->physicalFileExists = file_exists($this->absolutePath);
103
    }
104
105
    public function toArray(): array
106
    {
107
        $cachekey = $this->getCacheKey();
108
        if (! $this->hasCacheKey($cachekey) || $this->noCache) {
109
            $this->collateIntel();
110
            if ($this->debug) {
111
                if ($this->intel['ErrorHasAnyError']) {
112
                    echo PHP_EOL . 'x ' . $this->path . ' ' . PHP_EOL;
113
                } else {
114
                    echo '✓ ';
115
                }
116
            }
117
            $this->setCacheValue($cachekey, $this->intel);
118
        } else {
119
            $this->intel = $this->getCacheValue($cachekey);
120
        }
121
122
        return $this->intel;
123
    }
124
125
    protected function collateIntel(): void
126
    {
127
128
        $this->addFileSystemDetails();
129
        $this->addImageDetails();
130
131
        $obj = AllFilesInfo::inst();
132
        $dbFileData = $obj->getAnyData($this->intel['Path']);
133
        $this->addFolderDetails($dbFileData);
134
        $this->addDBDetails($dbFileData);
135
        $this->addCalculatedValues();
136
        $this->addHumanValues();
137
        ksort($this->intel);
138
    }
139
140
    protected function isRegularImage(string $extension): bool
141
    {
142
        return in_array(
143
            strtolower($extension),
144
            ['jpg', 'gif', 'png', 'webp', 'jpeg', 'bmp', 'tiff', 'svg'],
145
            true
146
        );
147
    }
148
149
    protected function isImage(string $absolutePath): bool
150
    {
151
        try {
152
            $outcome = (bool) @exif_imagetype($absolutePath);
153
        } catch (Exception $exception) {
154
            $outcome = false;
155
        }
156
157
        return $outcome;
158
    }
159
160
    protected function addFileSystemDetails()
161
    {
162
        //get path parts
163
        $this->parthParts = [];
164
        if ($this->physicalFileExists) {
165
            $this->parthParts = pathinfo($this->absolutePath);
0 ignored issues
show
Documentation Bug introduced by
It seems like pathinfo($this->absolutePath) can also be of type string. However, the property $parthParts is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
166
        }
167
168
        $this->parthParts['extension'] = $this->parthParts['extension'] ?? '';
169
        $this->parthParts['filename'] = $this->parthParts['filename'] ?? '';
170
        $this->parthParts['dirname'] = $this->parthParts['dirname'] ?? '';
171
172
        //basics!
173
        $this->intel['Path'] = $this->path;
174
        $this->intel['PathFolderPath'] = $this->parthParts['dirname'] ?: dirname($this->intel['Path']);
175
        //file name
176
        $this->intel['PathFileName'] = $this->parthParts['filename'] ?: basename($this->absolutePath);
177
        $this->intel['PathFileNameFirstLetter'] = strtoupper(substr((string) $this->intel['PathFileName'], 0, 1));
178
179
        //defaults
180
        $this->intel['ErrorIsInFileSystem'] = true;
181
        $this->intel['PathFileSize'] = 0;
182
        $this->intel['IsDir'] = is_dir($this->absolutePath);
183
184
        //in file details
185
        if ($this->physicalFileExists) {
186
            $this->intel['ErrorIsInFileSystem'] = false;
187
            $this->intel['PathFileSize'] = filesize($this->absolutePath);
188
        }
189
190
        //path
191
        $this->intel['PathFromPublicRoot'] = trim(str_replace($this->getPublicBaseFolder(), '', (string) $this->absolutePath), DIRECTORY_SEPARATOR);
192
        $this->intel['Path'] = trim($this->path, '/');
193
        $this->intel['AbsolutePath'] = Controller::join_links(ASSETS_PATH, $this->intel['Path']);
194
        $this->intel['PathFolderFromAssets'] = dirname($this->path);
195
        if ('.' === $this->intel['PathFolderFromAssets']) {
196
            $this->intel['PathFolderFromAssets'] = '--in-root-folder--';
197
        }
198
199
        //folder
200
        // $relativeDirFromAssetsFolder = str_replace($this->getAssetsBaseFolder(), '', (string) $this->intel['PathFolderPath']);
201
202
        //extension
203
        $this->intel['PathExtension'] = $this->parthParts['extension'] ?: $this->getExtension($this->path);
204
        $this->intel['PathExtensionAsLower'] = (string) strtolower($this->intel['PathExtension']);
205
        $this->intel['ErrorExtensionMisMatch'] = $this->intel['PathExtension'] !== $this->intel['PathExtensionAsLower'];
206
        $pathExtensionWithDot = '.' . $this->intel['PathExtension'];
207
        $extensionLength = strlen((string) $pathExtensionWithDot);
208
        $pathLength = strlen((string) $this->intel['PathFileName']);
209
        if (substr((string) $this->intel['PathFileName'], (-1 * $extensionLength)) === $pathExtensionWithDot) {
210
            $this->intel['PathFileName'] = substr((string) $this->intel['PathFileName'], 0, ($pathLength - $extensionLength));
211
        }
212
213
        $this->intel['ErrorInvalidExtension'] = false;
214
        if (false !== $this->intel['IsDir']) {
215
            $test = Injector::inst()->get(DBFile::class);
216
            $validationResult = Injector::inst()->get(ValidationResult::class);
217
            $this->intel['ErrorInvalidExtension'] = (bool) $test->validate(
218
                $validationResult,
219
                $this->intel['PathFileName']
220
            );
221
        }
222
    }
223
224
    protected function addImageDetails()
225
    {
226
        $this->intel['ImageRatio'] = '0';
227
        $this->intel['ImagePixels'] = 'n/a';
228
        $this->intel['ImageIsImage'] = $this->isRegularImage($this->intel['PathExtension']);
229
        $this->intel['ImageIsRegularImage'] = false;
230
        $this->intel['ImageWidth'] = 0;
231
        $this->intel['ImageHeight'] = 0;
232
        $this->intel['ImageType'] = $this->intel['PathExtension'];
233
        $this->intel['ImageAttribute'] = 'n/a';
234
235
        if ($this->physicalFileExists) {
236
            $this->intel['ImageIsRegularImage'] = $this->isRegularImage($this->intel['PathExtension']);
237
            $this->intel['ImageIsImage'] = $this->intel['ImageIsRegularImage'] ? true : $this->isImage($this->absolutePath);
238
            if ($this->intel['ImageIsImage']) {
239
                try {
240
                    list($width, $height, $type, $attr) = getimagesize($this->absolutePath);
241
                } catch (Exception $exception) {
242
                    $width = 0;
243
                    $height = 0;
244
                    $type = 0;
245
                    $attr = [];
246
                }
247
                $this->intel['ImageAttribute'] = print_r($attr, 1);
248
                $this->intel['ImageWidth'] = $width;
249
                $this->intel['ImageHeight'] = $height;
250
                if ($height > 0) {
251
                    $this->intel['ImageRatio'] = round($width / $height, 3);
252
                } else {
253
                    $this->intel['ImageRatio'] = 0;
254
                }
255
                $this->intel['ImagePixels'] = $width * $height;
256
                $this->intel['ImageType'] = $type;
257
                $this->intel['IsResizedImage'] = (bool) strpos($this->intel['PathFileName'], '__');
258
            }
259
        }
260
    }
261
262
    protected function addFolderDetails($dbFileData)
263
    {
264
        $folder = [];
265
        if (! empty($dbFileData['ParentID'])) {
266
            if (isset($this->folderCache[$dbFileData['ParentID']])) {
267
                $folder = $this->folderCache[$dbFileData['ParentID']];
268
            } else {
269
                $sql = 'SELECT * FROM "File" WHERE "ID" = ' . $dbFileData['ParentID'];
270
                $rows = DB::query($sql);
271
                foreach ($rows as $folder) {
272
                    $this->folderCache[$dbFileData['ParentID']] = $folder;
273
                }
274
            }
275
        }
276
277
        if (empty($folder)) {
278
            $this->intel['ErrorFindingFolder'] = ! empty($dbFileData['ParentID']);
279
            $this->intel['FolderID'] = 0;
280
        } else {
281
            $this->intel['ErrorFindingFolder'] = false;
282
            $this->intel['FolderID'] = $folder['ID'];
283
        }
284
285
        $this->intel['FolderCMSEditLink'] = '/admin/assets/show/' . $this->intel['FolderID'] . '/';
286
    }
287
288
    protected function addDBDetails($dbFileData)
289
    {
290
        $time = 0;
291
        if (empty($dbFileData)) {
292
            $this->intel['ErrorParentID'] = false;
293
            $this->intel['DBID'] = 0;
294
            $this->intel['DBClassName'] = 'Not in database';
295
            $this->intel['DBParentID'] = 0;
296
            $this->intel['DBPath'] = '';
297
            $this->intel['DBFilenameSS3'] = '';
298
            $this->intel['DBFilenameSS4'] = '';
299
            $this->intel['ErrorDBNotPresentStaging'] = true;
300
            $this->intel['ErrorDBNotPresentLive'] = true;
301
            $this->intel['ErrorInDraftOnly'] = false;
302
            $this->intel['ErrorNotInDraft'] = false;
303
            $this->intel['DBCMSEditLink'] = '/admin/assets/';
304
            $this->intel['DBTitle'] = '-- no title set in database';
305
            $this->intel['ErrorInFilename'] = false;
306
            $this->intel['ErrorInSs3Ss4Comparison'] = false;
307
            if ($this->physicalFileExists) {
308
                $time = filemtime($this->absolutePath);
309
            }
310
        } else {
311
            $obj = AllFilesInfo::inst();
312
            if (!$this->intel['IsDir']) {
313
                $this->intel['IsDir'] = is_a($dbFileData['ClassName'], Folder::class, true);
314
            }
315
            $dbFileData['Filename'] = $dbFileData['Filename'] ?? '';
316
            $this->intel['DBID'] = $dbFileData['ID'];
317
            $this->intel['DBClassName'] = $dbFileData['ClassName'];
318
            $this->intel['DBParentID'] = $dbFileData['ParentID'];
319
            $this->intel['DBPath'] = $dbFileData['FileFilename'] ?? $dbFileData['Filename'] ?? '';
320
            $this->intel['DBFilename'] = $dbFileData['Name'] ?: basename($this->intel['DBPath']);
321
            $existsOnStaging = $obj->existsOnStaging($this->intel['DBID']);
322
            $existsOnLive = $obj->existsOnLive($this->intel['DBID']);
323
            $this->intel['ErrorDBNotPresentStaging'] = ! $existsOnStaging;
324
            $this->intel['ErrorDBNotPresentLive'] = ! $existsOnLive;
325
            $this->intel['ErrorInDraftOnly'] = $existsOnStaging && ! $existsOnLive;
326
            $this->intel['ErrorNotInDraft'] = ! $existsOnStaging && $existsOnLive;
327
            $this->intel['DBCMSEditLink'] = '/admin/assets/EditForm/field/File/item/' . $this->intel['DBID'] . '/edit';
328
            $this->intel['DBTitle'] = $dbFileData['Title'];
329
            $this->intel['DBFilenameSS4'] = $dbFileData['FileFilename'] ?? 'none';
330
            $this->intel['DBFilenameSS3'] = $dbFileData['Filename'] ?? 'none';;
331
            $this->intel['ErrorInFilename'] = $this->intel['Path'] !== $this->intel['DBPath'];
332
            $ss3FileName = $dbFileData['Filename'] ?? '';
333
            if ('assets/' === substr((string) $ss3FileName, 0, strlen('assets/'))) {
334
                $ss3FileName = substr((string) $ss3FileName, strlen('assets/'));
0 ignored issues
show
Unused Code introduced by
The assignment to $ss3FileName is dead and can be removed.
Loading history...
335
            }
336
337
            $this->intel['ErrorInSs3Ss4Comparison'] = $this->intel['DBFilenameSS3'] && $this->intel['DBFilenameSS4'] !== $this->intel['DBFilenameSS3'];
338
            $time = strtotime((string) $dbFileData['LastEdited']);
339
            $this->intel['ErrorParentID'] = true;
340
            if (0 === (int) $this->intel['FolderID']) {
341
                $this->intel['ErrorParentID'] = (bool) (int) $dbFileData['ParentID'];
342
            } elseif ($this->intel['FolderID']) {
343
                $this->intel['ErrorParentID'] = (int) $this->intel['FolderID'] !== (int) $dbFileData['ParentID'];
344
            }
345
        }
346
347
        $this->intel['ErrorDBNotPresent'] = $this->intel['ErrorDBNotPresentLive'] && $this->intel['ErrorDBNotPresentStaging'];
348
349
        $this->intel['DBLastEditedTS'] = $time;
350
        $this->intel['DBLastEdited'] = DBDate::create_field(DBDatetime::class, $time)->Ago();
351
352
        $this->intel['DBTitleFirstLetter'] = strtoupper(substr((string) $this->intel['DBTitle'], 0, 1));
353
    }
354
355
    protected function addHumanValues()
356
    {
357
        $this->intel['HumanImageDimensions'] = $this->intel['ImageWidth'] . 'px wide by ' . $this->intel['ImageHeight'] . 'px high';
358
        $this->intel['HumanImageIsImage'] = $this->intel['ImageIsImage'] ? 'Is image' : 'Is not an image';
359
        $this->intel['HumanErrorExtensionMisMatch'] = $this->intel['ErrorExtensionMisMatch'] ?
360
            'irregular extension' : 'normal extension';
361
362
        //file size
363
        $this->intel['HumanFileSize'] = $this->humanFileSize($this->intel['PathFileSize']);
364
        $this->intel['HumanFileSizeRounded'] = '~ ' . $this->humanFileSize(round($this->intel['PathFileSize'] / 204800) * 204800);
0 ignored issues
show
Bug introduced by
round($this->intel['Path...ze'] / 204800) * 204800 of type double is incompatible with the type integer expected by parameter $bytes of Sunnysideup\AssetsOvervi...leInfo::humanFileSize(). ( Ignorable by Annotation )

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

364
        $this->intel['HumanFileSizeRounded'] = '~ ' . $this->humanFileSize(/** @scrutinizer ignore-type */ round($this->intel['PathFileSize'] / 204800) * 204800);
Loading history...
365
        $this->intel['HumanErrorIsInFileSystem'] = $this->intel['ErrorIsInFileSystem'] ? 'File does not exist' : 'File exists';
366
367
        $this->intel['HumanFolderIsInOrder'] = $this->intel['FolderID'] ? 'In sub-folder' : 'In root folder';
368
369
        $this->intel['HumanErrorInFilename'] = $this->intel['ErrorInFilename'] ? 'Error in filename case' : 'No error in filename case';
370
        $this->intel['HumanErrorParentID'] = $this->intel['ErrorParentID'] ? 'Error in folder ID' : 'Perfect folder ID';
371
        $stageDBStatus = $this->intel['ErrorDBNotPresentStaging'] ? 'Is not on draft site' : ' Is on draft site';
372
        $liveDBStatus = $this->intel['ErrorDBNotPresentLive'] ? 'Is not on live site' : ' Is on live site';
373
        $this->intel['HumanErrorDBNotPresent'] = $stageDBStatus . ', ' . $liveDBStatus;
374
        $this->intel['HumanErrorInSs3Ss4Comparison'] = $this->intel['ErrorInSs3Ss4Comparison'] ?
375
            'Filename and FileFilename do not match' : 'Filename and FileFilename match';
376
        $this->intel['HumanIcon'] = File::get_icon_for_extension($this->intel['PathExtensionAsLower']);
377
    }
378
379
    protected function addCalculatedValues()
380
    {
381
        $this->intel['ErrorHasAnyError'] = false;
382
        foreach ($this->errorFields as $field) {
383
            if ($this->intel[$field]) {
384
                $this->intel['ErrorHasAnyError'] = true;
385
            }
386
        }
387
    }
388
389
    // protected function getBackupDataObject()
390
    // {
391
    //     $file = DataObject::get_one(File::class, ['FileFilename' => $this->intel['Path']]);
392
    //     //backup for file ...
393
    //     if (! $file) {
394
    //         if ($folder) {
395
    //             $nameInDB = $this->intel['PathFileName'] . '.' . $this->intel['PathExtension'];
396
    //             $file = DataObject::get_one(File::class, ['Name' => $nameInDB, 'ParentID' => $folder->ID]);
397
    //         }
398
    //     }
399
    //     $filter = ['FileFilename' => $this->intel['PathFolderFromAssets']];
400
    //     if (Folder::get()->filter($filter)->count() === 1) {
401
    //         $folder = DataObject::get_one(Folder::class, $filter);
402
    //     }
403
    //
404
    //     return $file;
405
    // }
406
407
    //#############################################
408
    // CACHE
409
    //#############################################
410
411
    protected function getCacheKey(): string
412
    {
413
        return $this->pathHash;
414
    }
415
}
416