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

OneFileInfo::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 2
eloc 4
c 3
b 1
f 0
nc 2
nop 2
dl 0
loc 6
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, ?bool $exists = null): OneFileInfo
34
    {
35
        if (!isset(self::$cached_inst[$path])) {
36
            self::$cached_inst[$path] = new OneFileInfo($path, $exists);
37
        }
38
        return self::$cached_inst[$path];
39
    }
40
41
    protected bool $debug = false;
42
43
    protected array $errorFields = [
44
        'ErrorDBNotPresent',
45
        'ErrorDBNotPresentLive',
46
        'ErrorDBNotPresentStaging',
47
        'ErrorExtensionMisMatch',
48
        'ErrorFindingFolder',
49
        'ErrorInFilename',
50
        'ErrorInSs3Ss4Comparison',
51
        'ErrorParentID',
52
        'ErrorInDraftOnly',
53
        'ErrorNotInDraft',
54
    ];
55
56
    /**
57
     * @var string
58
     */
59
    protected string $hash = '';
60
61
    /**
62
     * @var string
63
     */
64
    protected string $path = '';
65
    protected string $absoletePath = '';
66
67
    /**
68
     * @var array
69
     */
70
    protected array $intel = [];
71
72
    /**
73
     * @var array
74
     */
75
    protected array $parthParts = [];
76
77
    /**
78
     * @var bool
79
     */
80
    protected bool $fileExists = false;
81
82
    protected array $folderCache = [];
83
84
    public function __construct(string $location, ?bool $fileExists = null)
85
    {
86
        $this->path = $location;
87
        $this->absoletePath = ASSETS_PATH . DIRECTORY_SEPARATOR . $this->path;
88
        $this->hash = md5($this->path);
89
        $this->fileExists = null === $fileExists ? file_exists($this->absoletePath) : $fileExists;
90
    }
91
92
    public function toArray(?bool $recalc = false): array
93
    {
94
        $cachekey = $this->getCacheKey();
95
        if (! $this->hasCacheKey($cachekey) || $recalc) {
96
            $this->getUncachedIntel();
97
            if ($this->debug) {
98
                echo $this->intel['ErrorHasAnyError'] ? 'x ' : '✓ ';
99
            }
100
            $this->setCacheValue($cachekey, $this->intel);
101
        } else {
102
            $this->intel = $this->getCacheValue($cachekey);
103
        }
104
105
        return $this->intel;
106
    }
107
108
    protected function getUncachedIntel(): void
109
    {
110
111
        $this->addFileSystemDetails();
112
        $this->addImageDetails();
113
114
        $obj = AllFilesInfo::inst();
115
        $dbFileData = $obj->getAnyData($this->intel['Path']);
116
        $this->addFolderDetails($dbFileData);
117
        $this->addDBDetails($dbFileData);
118
        $this->addCalculatedValues();
119
        $this->addHumanValues();
120
        ksort($this->intel);
121
    }
122
123
    protected function isRegularImage(string $extension): bool
124
    {
125
        return in_array(
126
            strtolower($extension),
127
            ['jpg', 'gif', 'png', 'webp', 'jpeg', 'bmp', 'tiff', 'svg'],
128
            true
129
        );
130
    }
131
132
    protected function isImage(string $absolutePath): bool
133
    {
134
        try {
135
            $outcome = (bool) @exif_imagetype($absolutePath);
136
        } catch (Exception $exception) {
137
            $outcome = false;
138
        }
139
140
        return $outcome;
141
    }
142
143
    protected function addFileSystemDetails()
144
    {
145
        //get path parts
146
        $this->parthParts = [];
147
        if ($this->fileExists) {
148
            $this->parthParts = pathinfo($this->absoletePath);
0 ignored issues
show
Documentation Bug introduced by
It seems like pathinfo($this->absoletePath) 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...
149
        }
150
151
        $this->parthParts['extension'] = $this->parthParts['extension'] ?? '';
152
        $this->parthParts['filename'] = $this->parthParts['filename'] ?? '';
153
        $this->parthParts['dirname'] = $this->parthParts['dirname'] ?? '';
154
155
        //basics!
156
        $this->intel['Path'] = $this->path;
157
        $this->intel['PathFolderPath'] = $this->parthParts['dirname'] ?: dirname($this->intel['Path']);
158
        //file name
159
        $this->intel['PathFileName'] = $this->parthParts['filename'] ?: basename($this->absoletePath);
160
        $this->intel['PathFileNameFirstLetter'] = strtoupper(substr((string) $this->intel['PathFileName'], 0, 1));
161
162
        //defaults
163
        $this->intel['ErrorIsInFileSystem'] = true;
164
        $this->intel['PathFileSize'] = 0;
165
        $this->intel['IsDir'] = is_dir($this->absoletePath);
166
167
        //in file details
168
        if ($this->fileExists) {
169
            $this->intel['ErrorIsInFileSystem'] = false;
170
            $this->intel['PathFileSize'] = filesize($this->absoletePath);
171
        }
172
173
        //path
174
        $this->intel['PathFromPublicRoot'] = trim(str_replace($this->getPublicBaseFolder(), '', (string) $this->absoletePath), DIRECTORY_SEPARATOR);
175
        $this->intel['Path'] = trim($this->path, '/');
176
        $this->intel['AbsoletePath'] = Controller::join_links(ASSETS_PATH, $this->intel['Path']);
177
        $this->intel['PathFolderFromAssets'] = dirname($this->path);
178
        if ('.' === $this->intel['PathFolderFromAssets']) {
179
            $this->intel['PathFolderFromAssets'] = '--in-root-folder--';
180
        }
181
182
        //folder
183
        // $relativeDirFromAssetsFolder = str_replace($this->getAssetsBaseFolder(), '', (string) $this->intel['PathFolderPath']);
184
185
        //extension
186
        $this->intel['PathExtension'] = $this->parthParts['extension'] ?: $this->getExtension($this->path);
187
        $this->intel['PathExtensionAsLower'] = (string) strtolower($this->intel['PathExtension']);
188
        $this->intel['ErrorExtensionMisMatch'] = $this->intel['PathExtension'] !== $this->intel['PathExtensionAsLower'];
189
        $pathExtensionWithDot = '.' . $this->intel['PathExtension'];
190
        $extensionLength = strlen((string) $pathExtensionWithDot);
191
        $pathLength = strlen((string) $this->intel['PathFileName']);
192
        if (substr((string) $this->intel['PathFileName'], (-1 * $extensionLength)) === $pathExtensionWithDot) {
193
            $this->intel['PathFileName'] = substr((string) $this->intel['PathFileName'], 0, ($pathLength - $extensionLength));
194
        }
195
196
        $this->intel['ErrorInvalidExtension'] = false;
197
        if (false !== $this->intel['IsDir']) {
198
            $test = Injector::inst()->get(DBFile::class);
199
            $validationResult = Injector::inst()->get(ValidationResult::class);
200
            $this->intel['ErrorInvalidExtension'] = (bool) $test->validate(
201
                $validationResult,
202
                $this->intel['PathFileName']
203
            );
204
        }
205
    }
206
207
    protected function addImageDetails()
208
    {
209
        $this->intel['ImageRatio'] = '0';
210
        $this->intel['ImagePixels'] = 'n/a';
211
        $this->intel['ImageIsImage'] = $this->isRegularImage($this->intel['PathExtension']);
212
        $this->intel['ImageIsRegularImage'] = false;
213
        $this->intel['ImageWidth'] = 0;
214
        $this->intel['ImageHeight'] = 0;
215
        $this->intel['ImageType'] = $this->intel['PathExtension'];
216
        $this->intel['ImageAttribute'] = 'n/a';
217
218
        if ($this->fileExists) {
219
            $this->intel['ImageIsRegularImage'] = $this->isRegularImage($this->intel['PathExtension']);
220
            $this->intel['ImageIsImage'] = $this->intel['ImageIsRegularImage'] ? true : $this->isImage($this->absoletePath);
221
            if ($this->intel['ImageIsImage']) {
222
                try {
223
                    list($width, $height, $type, $attr) = getimagesize($this->absoletePath);
224
                } catch (Exception $exception) {
225
                    $width = 0;
226
                    $height = 0;
227
                    $type = 0;
228
                    $attr = [];
229
                }
230
                $this->intel['ImageAttribute'] = print_r($attr, 1);
231
                $this->intel['ImageWidth'] = $width;
232
                $this->intel['ImageHeight'] = $height;
233
                if ($height > 0) {
234
                    $this->intel['ImageRatio'] = round($width / $height, 3);
235
                } else {
236
                    $this->intel['ImageRatio'] = 0;
237
                }
238
                $this->intel['ImagePixels'] = $width * $height;
239
                $this->intel['ImageType'] = $type;
240
                $this->intel['IsResizedImage'] = (bool) strpos($this->intel['PathFileName'], '__');
241
            }
242
        }
243
    }
244
245
    protected function addFolderDetails($dbFileData)
246
    {
247
        $folder = [];
248
        if (! empty($dbFileData['ParentID'])) {
249
            if (isset($this->folderCache[$dbFileData['ParentID']])) {
250
                $folder = $this->folderCache[$dbFileData['ParentID']];
251
            } else {
252
                $sql = 'SELECT * FROM "File" WHERE "ID" = ' . $dbFileData['ParentID'];
253
                $rows = DB::query($sql);
254
                foreach ($rows as $folder) {
255
                    $this->folderCache[$dbFileData['ParentID']] = $folder;
256
                }
257
            }
258
        }
259
260
        if (empty($folder)) {
261
            $this->intel['ErrorFindingFolder'] = ! empty($dbFileData['ParentID']);
262
            $this->intel['FolderID'] = 0;
263
        } else {
264
            $this->intel['ErrorFindingFolder'] = false;
265
            $this->intel['FolderID'] = $folder['ID'];
266
        }
267
268
        $this->intel['FolderCMSEditLink'] = '/admin/assets/show/' . $this->intel['FolderID'] . '/';
269
    }
270
271
    protected function addDBDetails($dbFileData)
272
    {
273
        $time = 0;
274
        if (empty($dbFileData)) {
275
            $this->intel['ErrorParentID'] = false;
276
            $this->intel['DBID'] = 0;
277
            $this->intel['DBClassName'] = 'Not in database';
278
            $this->intel['DBParentID'] = 0;
279
            $this->intel['DBPath'] = '';
280
            $this->intel['DBFilenameSS3'] = '';
281
            $this->intel['DBFilenameSS4'] = '';
282
            $this->intel['ErrorDBNotPresentStaging'] = true;
283
            $this->intel['ErrorDBNotPresentLive'] = true;
284
            $this->intel['ErrorInDraftOnly'] = false;
285
            $this->intel['ErrorNotInDraft'] = false;
286
            $this->intel['DBCMSEditLink'] = '/admin/assets/';
287
            $this->intel['DBTitle'] = '-- no title set in database';
288
            $this->intel['ErrorInFilename'] = false;
289
            $this->intel['ErrorInSs3Ss4Comparison'] = false;
290
            if ($this->fileExists) {
291
                $time = filemtime($this->absoletePath);
292
            }
293
        } else {
294
            $obj = AllFilesInfo::inst();
295
            $dbFileData['Filename'] = $dbFileData['Filename'] ?? '';
296
            $this->intel['DBID'] = $dbFileData['ID'];
297
            $this->intel['DBClassName'] = $dbFileData['ClassName'];
298
            $this->intel['DBParentID'] = $dbFileData['ParentID'];
299
            $this->intel['DBPath'] = $dbFileData['FileFilename'] ?? $dbFileData['Filename'] ?? '';
300
            $this->intel['DBFilename'] = $dbFileData['Name'] ?: basename($this->intel['DBPath']);
301
            $existsOnStaging = $obj->existsOnStaging($this->intel['DBID']);
302
            $existsOnLive = $obj->existsOnLive($this->intel['DBID']);
303
            $this->intel['ErrorDBNotPresentStaging'] = ! $existsOnStaging;
304
            $this->intel['ErrorDBNotPresentLive'] = ! $existsOnLive;
305
            $this->intel['ErrorInDraftOnly'] = $existsOnStaging && ! $existsOnLive;
306
            $this->intel['ErrorNotInDraft'] = ! $existsOnStaging && $existsOnLive;
307
            $this->intel['DBCMSEditLink'] = '/admin/assets/EditForm/field/File/item/' . $this->intel['DBID'] . '/edit';
308
            $this->intel['DBTitle'] = $dbFileData['Title'];
309
            $this->intel['DBFilenameSS4'] = $dbFileData['FileFilename'];
310
            $this->intel['DBFilenameSS3'] = $dbFileData['Filename'];
311
            $this->intel['ErrorInFilename'] = $this->intel['Path'] !== $this->intel['DBPath'];
312
            $ss3FileName = $dbFileData['Filename'] ?? '';
313
            if ('assets/' === substr((string) $ss3FileName, 0, strlen('assets/'))) {
314
                $ss3FileName = substr((string) $ss3FileName, strlen('assets/'));
315
            }
316
317
            $this->intel['ErrorInSs3Ss4Comparison'] = $this->intel['DBFilenameSS3'] && $dbFileData['FileFilename'] !== $ss3FileName;
318
            $time = strtotime((string) $dbFileData['LastEdited']);
319
            $this->intel['ErrorParentID'] = true;
320
            if (0 === (int) $this->intel['FolderID']) {
321
                $this->intel['ErrorParentID'] = (bool) (int) $dbFileData['ParentID'];
322
            } elseif ($this->intel['FolderID']) {
323
                $this->intel['ErrorParentID'] = (int) $this->intel['FolderID'] !== (int) $dbFileData['ParentID'];
324
            }
325
        }
326
327
        $this->intel['ErrorDBNotPresent'] = $this->intel['ErrorDBNotPresentLive'] && $this->intel['ErrorDBNotPresentStaging'];
328
329
        $this->intel['DBLastEditedTS'] = $time;
330
        $this->intel['DBLastEdited'] = DBDate::create_field(DBDatetime::class, $time)->Ago();
331
332
        $this->intel['DBTitleFirstLetter'] = strtoupper(substr((string) $this->intel['DBTitle'], 0, 1));
333
    }
334
335
    protected function addHumanValues()
336
    {
337
        $this->intel['HumanImageDimensions'] = $this->intel['ImageWidth'] . 'px wide by ' . $this->intel['ImageHeight'] . 'px high';
338
        $this->intel['HumanImageIsImage'] = $this->intel['ImageIsImage'] ? 'Is image' : 'Is not an image';
339
        $this->intel['HumanErrorExtensionMisMatch'] = $this->intel['ErrorExtensionMisMatch'] ?
340
            'irregular extension' : 'normal extension';
341
342
        //file size
343
        $this->intel['HumanFileSize'] = $this->humanFileSize($this->intel['PathFileSize']);
344
        $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

344
        $this->intel['HumanFileSizeRounded'] = '~ ' . $this->humanFileSize(/** @scrutinizer ignore-type */ round($this->intel['PathFileSize'] / 204800) * 204800);
Loading history...
345
        $this->intel['HumanErrorIsInFileSystem'] = $this->intel['ErrorIsInFileSystem'] ? 'File does not exist' : 'File exists';
346
347
        $this->intel['HumanFolderIsInOrder'] = $this->intel['FolderID'] ? 'In sub-folder' : 'In root folder';
348
349
        $this->intel['HumanErrorInFilename'] = $this->intel['ErrorInFilename'] ? 'Error in filename case' : 'No error in filename case';
350
        $this->intel['HumanErrorParentID'] = $this->intel['ErrorParentID'] ? 'Error in folder ID' : 'Perfect folder ID';
351
        $stageDBStatus = $this->intel['ErrorDBNotPresentStaging'] ? 'Is not on draft site' : ' Is on draft site';
352
        $liveDBStatus = $this->intel['ErrorDBNotPresentLive'] ? 'Is not on live site' : ' Is on live site';
353
        $this->intel['HumanErrorDBNotPresent'] = $stageDBStatus . ', ' . $liveDBStatus;
354
        $this->intel['HumanErrorInSs3Ss4Comparison'] = $this->intel['ErrorInSs3Ss4Comparison'] ?
355
            'Filename and FileFilename do not match' : 'Filename and FileFilename match';
356
        $this->intel['HumanIcon'] = File::get_icon_for_extension($this->intel['PathExtensionAsLower']);
357
    }
358
359
    protected function addCalculatedValues()
360
    {
361
        $this->intel['ErrorHasAnyError'] = false;
362
        foreach ($this->errorFields as $field) {
363
            if ($this->intel[$field]) {
364
                $this->intel['ErrorHasAnyError'] = true;
365
            }
366
        }
367
    }
368
369
    // protected function getBackupDataObject()
370
    // {
371
    //     $file = DataObject::get_one(File::class, ['FileFilename' => $this->intel['Path']]);
372
    //     //backup for file ...
373
    //     if (! $file) {
374
    //         if ($folder) {
375
    //             $nameInDB = $this->intel['PathFileName'] . '.' . $this->intel['PathExtension'];
376
    //             $file = DataObject::get_one(File::class, ['Name' => $nameInDB, 'ParentID' => $folder->ID]);
377
    //         }
378
    //     }
379
    //     $filter = ['FileFilename' => $this->intel['PathFolderFromAssets']];
380
    //     if (Folder::get()->filter($filter)->count() === 1) {
381
    //         $folder = DataObject::get_one(Folder::class, $filter);
382
    //     }
383
    //
384
    //     return $file;
385
    // }
386
387
    //#############################################
388
    // CACHE
389
    //#############################################
390
391
    protected function getCacheKey(): string
392
    {
393
        return $this->hash;
394
    }
395
}
396