Passed
Push — master ( 9097b3...c384c0 )
by Nicolaas
02:31
created

OneFileInfo::addImageDetails()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 34
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

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

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