Issues (35)

src/Files/OneFileInfo.php (2 issues)

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\Image;
9
use SilverStripe\Assets\Storage\DBFile;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Core\Config\Config;
12
use SilverStripe\Core\Config\Configurable;
13
use SilverStripe\Core\Injector\Injectable;
14
use SilverStripe\Core\Injector\Injector;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\ORM\DB;
17
use SilverStripe\ORM\FieldType\DBDate;
18
use SilverStripe\ORM\FieldType\DBDatetime;
19
use SilverStripe\ORM\ValidationResult;
20
use Sunnysideup\AssetsOverview\Control\View;
21
use Sunnysideup\AssetsOverview\Interfaces\FileInfo;
22
use Sunnysideup\AssetsOverview\Traits\Cacher;
23
use Sunnysideup\AssetsOverview\Traits\FilesystemRelatedTraits;
24
use Sunnysideup\Flush\FlushNow;
25
26
class OneFileInfo implements FileInfo
27
{
28
    use FilesystemRelatedTraits;
29
    use Injectable;
30
    use Configurable;
31
    use Cacher;
32
    use FlushNow;
33
34
    protected static array $cached_inst = [];
35
36
    public static function inst(string $path): OneFileInfo
37
    {
38
        if (!isset(self::$cached_inst[$path])) {
39
            self::$cached_inst[$path] = new OneFileInfo($path);
40
        }
41
        return self::$cached_inst[$path];
42
    }
43
44
    protected bool $debug = false;
45
    protected bool $noCache = false;
46
47
    public function setDebug(bool $b): static
48
    {
49
        $this->debug = $b;
50
        return $this;
51
    }
52
53
    public function setNoCache(bool $b): static
54
    {
55
        $this->noCache = $b;
56
        return $this;
57
    }
58
59
    protected array $errorFields = [
60
        'ErrorDBNotPresent',
61
        'ErrorDBNotPresentStaging',
62
        'ErrorExtensionMisMatch',
63
        'ErrorFindingFolder',
64
        'ErrorInFilename',
65
        'ErrorInSs3Ss4Comparison',
66
        'ErrorParentID',
67
        'ErrorNotInDraft',
68
    ];
69
70
    /**
71
     * @var string
72
     */
73
    protected string $pathHash = '';
74
75
    /**
76
     * @var string
77
     */
78
    protected string $path = '';
79
80
    /**
81
     * @var array
82
     */
83
    protected array $intel = [];
84
85
    /**
86
     * @var bool
87
     */
88
    protected bool $physicalFileExists = false;
89
    protected ?File $file;
90
91
    public function __construct(string $location)
92
    {
93
        $this->path = $location;
94
        $this->intel['Path'] = $this->path;
95
        $this->intel['AbsolutePath'] = Controller::join_links(ASSETS_PATH, $this->intel['Path']);
96
        $this->intel['IsProtected'] = false;
97
        $this->pathHash = md5($this->path);
98
        $this->physicalFileExists = file_exists($this->intel['AbsolutePath']);
99
        $this->file = DataObject::get_one(File::class, ['FileFilename' => $this->path]);
100
        if (!$this->physicalFileExists) {
101
            if ($this->file && $this->file->exists()) {
102
                $this->physicalFileExists = true;
103
                $this->intel['IsProtected'] = true;
104
            }
105
        }
106
    }
107
108
    public function toArray(): array
109
    {
110
        $cachekey = $this->getCacheKey();
111
        if (! $this->hasCacheKey($cachekey) || $this->noCache) {
112
            $this->collateIntel();
113
            if ($this->debug) {
114
                if ($this->intel['ErrorHasAnyError']) {
115
                    echo PHP_EOL . 'x ' . $this->path . ' ' . PHP_EOL;
116
                } else {
117
                    echo '✓ ';
118
                }
119
            }
120
            $this->setCacheValue($cachekey, $this->intel);
121
        } else {
122
            $this->intel = $this->getCacheValue($cachekey);
123
        }
124
125
        return $this->intel;
126
    }
127
128
    protected function collateIntel(): void
129
    {
130
131
        $this->addFileSystemDetails();
132
        $this->addImageDetails();
133
134
        $obj = AllFilesInfo::inst();
135
        $dbFileData = $obj->getAnyData($this->intel['Path']);
136
        $this->addFolderDetails($dbFileData);
137
        $this->addDBDetails($dbFileData);
138
        $this->addCalculatedValues();
139
        $this->addHumanValues();
140
        $this->file = null;
141
        ksort($this->intel);
142
    }
143
144
    protected function isRegularImage(string $extension): bool
145
    {
146
        return in_array(
147
            strtolower($extension),
148
            ['jpg', 'gif', 'png', 'webp', 'jpeg', 'bmp', 'tiff', 'svg'],
149
            true
150
        );
151
    }
152
153
    protected function isImage(): bool
154
    {
155
        return $this->file && $this->file instanceof Image;
156
    }
157
158
    protected function addFileSystemDetails()
159
    {
160
        //get path parts
161
        $pathParts = [];
162
        if ($this->physicalFileExists) {
163
            $pathParts = pathinfo($this->intel['AbsolutePath']);
164
        }
165
166
        $pathParts['extension'] = $pathParts['extension'] ?? '';
167
        $pathParts['filename'] = $pathParts['filename'] ?? '';
168
        $pathParts['dirname'] = $pathParts['dirname'] ?? '';
169
170
        //basics!
171
        $this->intel['InfoLink'] = Config::inst()->get(View::class, 'url_segment') . '/jsonone/?path=' . $this->path;
172
        $this->intel['PathFolderPath'] = $pathParts['dirname'] ?: dirname($this->intel['Path']);
173
        //file name
174
        $this->intel['PathFileName'] = $pathParts['filename'] ?: basename($this->intel['AbsolutePath']);
175
        $this->intel['PathFileNameFirstLetter'] = strtoupper(substr((string) $this->intel['PathFileName'], 0, 1));
176
177
        //defaults
178
        $this->intel['ErrorIsInFileSystem'] = true;
179
        $this->intel['PathFileSize'] = 0;
180
        $this->intel['IsDir'] = is_dir($this->intel['AbsolutePath']);
181
182
        //in file details
183
        if ($this->physicalFileExists) {
184
            $this->intel['ErrorIsInFileSystem'] = false;
185
            $this->intel['PathFileSize'] = $this->file?->getAbsoluteSize() ?: 0;
186
        }
187
188
        //path
189
        $this->intel['PathFromPublicRoot'] = trim(str_replace($this->getPublicBaseFolder(), '', (string) $this->intel['AbsolutePath']), DIRECTORY_SEPARATOR);
190
        $this->intel['Path'] = trim($this->path, '/');
191
        $this->intel['PathFolderFromAssets'] = dirname($this->path);
192
        if ('.' === $this->intel['PathFolderFromAssets']) {
193
            $this->intel['PathFolderFromAssets'] = '--in-root-folder--';
194
        }
195
196
        //folder
197
        // $relativeDirFromAssetsFolder = str_replace($this->getAssetsBaseFolder(), '', (string) $this->intel['PathFolderPath']);
198
199
        //extension
200
        $this->intel['PathExtension'] = $pathParts['extension'] ?: $this->getExtension($this->path);
201
        $this->intel['PathExtensionAsLower'] = (string) strtolower($this->intel['PathExtension']);
202
        $this->intel['ErrorExtensionMisMatch'] = $this->intel['PathExtension'] !== $this->intel['PathExtensionAsLower'];
203
        $pathExtensionWithDot = '.' . $this->intel['PathExtension'];
204
        $extensionLength = strlen((string) $pathExtensionWithDot);
205
        $pathLength = strlen((string) $this->intel['PathFileName']);
206
        if (substr((string) $this->intel['PathFileName'], (-1 * $extensionLength)) === $pathExtensionWithDot) {
207
            $this->intel['PathFileName'] = substr((string) $this->intel['PathFileName'], 0, ($pathLength - $extensionLength));
208
        }
209
210
        $this->intel['ErrorInvalidExtension'] = false;
211
        if (false !== $this->intel['IsDir']) {
212
            $test = Injector::inst()->get(DBFile::class);
213
            $validationResult = Injector::inst()->get(ValidationResult::class);
214
            $this->intel['ErrorInvalidExtension'] = (bool) $test->validate(
215
                $validationResult,
216
                $this->intel['PathFileName']
217
            );
218
        }
219
    }
220
221
    protected function addImageDetails()
222
    {
223
        $this->intel['ImageRatio'] = '0';
224
        $this->intel['ImagePixels'] = 'n/a';
225
        $this->intel['IsImage'] = $this->isImage();
226
        $this->intel['ImageIsRegularImage'] = $this->isRegularImage($this->intel['PathExtension']);
227
        $this->intel['ImageWidth'] = 0;
228
        $this->intel['ImageHeight'] = 0;
229
        $this->intel['ImageType'] = $this->intel['PathExtension'];
230
        $this->intel['ImageAttribute'] = 'n/a';
231
232
        if ($this->physicalFileExists) {
233
            if ($this->intel['IsImage']) {
234
                $this->intel['ImageWidth'] = $this->file?->getWidth() ?: 0;
235
                $this->intel['ImageHeight'] = $this->file?->getHeight() ?: 0;
236
                if ($this->intel['ImageHeight'] > 0) {
237
                    $this->intel['ImageRatio'] = round($this->intel['ImageHeight'] / $this->intel['ImageWidth'], 3);
238
                } else {
239
                    $this->intel['ImageRatio'] = 0;
240
                }
241
                $this->intel['ImagePixels'] =  $this->intel['ImageHeight'] * $this->intel['ImageWidth'];
242
                $this->intel['IsResizedImage'] = (bool) strpos($this->intel['PathFileName'], '__');
243
            }
244
        }
245
    }
246
247
    protected function addFolderDetails($dbFileData)
248
    {
249
        $folder = [];
250
        $hasFolder = false;
251
        if (! empty($dbFileData['ParentID'])) {
252
            $sql = 'SELECT ID FROM "File" WHERE "ID" = ' . $dbFileData['ParentID'] . ' LIMIT 1';
253
            $rows = DB::query($sql);
254
            foreach ($rows as $folder) {
255
                $hasFolder = true;
256
                break;
257
            }
258
        }
259
260
        if ($hasFolder) {
261
            $this->intel['ErrorFindingFolder'] = false;
262
            $this->intel['FolderID'] = $folder['ID'];
263
            $this->intel['FolderCMSEditLink'] = '/admin/assets/show/' . $this->intel['FolderID'] . '/';
264
        } else {
265
            $this->intel['ErrorFindingFolder'] = ! empty($dbFileData['ParentID']);
266
            $this->intel['FolderID'] = 0;
267
            $this->intel['FolderCMSEditLink'] = '/admin/assets';
268
        }
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
            $time = time();
291
            if ($this->physicalFileExists && !$this->intel['IsProtected'] && $this->intel['AbsolutePath']) {
292
                if (file_exists($this->intel['AbsolutePath'])) {
293
                    $time = filemtime($this->intel['AbsolutePath']);
294
                }
295
            }
296
        } else {
297
            $obj = AllFilesInfo::inst();
298
            if (!$this->intel['IsDir']) {
299
                $this->intel['IsDir'] = is_a($dbFileData['ClassName'], Folder::class, true);
300
            }
301
            $dbFileData['Filename'] = $dbFileData['Filename'] ?? '';
302
            $this->intel['DBID'] = $dbFileData['ID'];
303
            $this->intel['DBClassName'] = $dbFileData['ClassName'];
304
            $this->intel['DBParentID'] = $dbFileData['ParentID'];
305
            $this->intel['DBPath'] = $dbFileData['FileFilename'] ?? $dbFileData['Filename'] ?? '';
306
            $this->intel['DBFilename'] = $dbFileData['Name'] ?: basename($this->intel['DBPath']);
307
            $existsOnStaging = $obj->existsOnStaging($this->intel['DBID']);
308
            $existsOnLive = $obj->existsOnLive($this->intel['DBID']);
309
            $this->intel['ErrorDBNotPresentStaging'] = ! $existsOnStaging;
310
            $this->intel['ErrorDBNotPresentLive'] = ! $existsOnLive;
311
            $this->intel['ErrorInDraftOnly'] = $existsOnStaging && ! $existsOnLive;
312
            $this->intel['ErrorNotInDraft'] = ! $existsOnStaging && $existsOnLive;
313
            $this->intel['DBCMSEditLink'] = '/admin/assets/EditForm/field/File/item/' . $this->intel['DBID'] . '/edit';
314
            $this->intel['DBTitle'] = $dbFileData['Title'];
315
            $this->intel['DBFilenameSS4'] = $dbFileData['FileFilename'] ?? 'none';
316
            $this->intel['DBFilenameSS3'] = $dbFileData['Filename'] ?? 'none';;
317
            $this->intel['ErrorInFilename'] = $this->intel['Path'] !== $this->intel['DBPath'];
318
            $ss3FileName = $dbFileData['Filename'] ?? '';
319
            if ('assets/' === substr((string) $ss3FileName, 0, strlen('assets/'))) {
320
                $ss3FileName = substr((string) $ss3FileName, strlen('assets/'));
0 ignored issues
show
The assignment to $ss3FileName is dead and can be removed.
Loading history...
321
            }
322
323
            $this->intel['ErrorInSs3Ss4Comparison'] = $this->intel['DBFilenameSS3'] && $this->intel['DBFilenameSS4'] !== $this->intel['DBFilenameSS3'];
324
            $time = strtotime((string) $dbFileData['LastEdited']);
325
            $this->intel['ErrorParentID'] = true;
326
            if (0 === (int) $this->intel['FolderID']) {
327
                $this->intel['ErrorParentID'] = (bool) (int) $dbFileData['ParentID'];
328
            } elseif ($this->intel['FolderID']) {
329
                $this->intel['ErrorParentID'] = (int) $this->intel['FolderID'] !== (int) $dbFileData['ParentID'];
330
            }
331
        }
332
333
        $this->intel['ErrorDBNotPresent'] = $this->intel['ErrorDBNotPresentLive'] && $this->intel['ErrorDBNotPresentStaging'];
334
335
        $this->intel['DBLastEditedTS'] = $time;
336
        $this->intel['DBLastEdited'] = DBDate::create_field(DBDatetime::class, $time)->Ago();
337
338
        $this->intel['DBTitleFirstLetter'] = strtoupper(substr((string) $this->intel['DBTitle'], 0, 1));
339
    }
340
341
    protected function addHumanValues()
342
    {
343
        $this->intel['HumanImageDimensions'] = $this->intel['ImageWidth'] . 'px wide by ' . $this->intel['ImageHeight'] . 'px high';
344
        $this->intel['HumanImageIsImage'] = $this->intel['IsImage'] ? 'Is image' : 'Is not an image';
345
        $this->intel['HumanErrorExtensionMisMatch'] = $this->intel['ErrorExtensionMisMatch'] ?
346
            'irregular extension' : 'normal extension';
347
348
        //file size
349
        $this->intel['HumanFileSize'] = $this->humanFileSize($this->intel['PathFileSize']);
350
        $this->intel['HumanFileSizeRounded'] = '~ ' . $this->humanFileSize(round($this->intel['PathFileSize'] / 204800) * 204800);
0 ignored issues
show
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

350
        $this->intel['HumanFileSizeRounded'] = '~ ' . $this->humanFileSize(/** @scrutinizer ignore-type */ round($this->intel['PathFileSize'] / 204800) * 204800);
Loading history...
351
        $this->intel['HumanErrorIsInFileSystem'] = $this->intel['ErrorIsInFileSystem'] ? 'File does not exist' : 'File exists';
352
353
        $this->intel['HumanFolderIsInOrder'] = $this->intel['FolderID'] ? 'In sub-folder' : 'In root folder';
354
355
        $this->intel['HumanErrorInFilename'] = $this->intel['ErrorInFilename'] ? 'Error in filename case' : 'No error in filename case';
356
        $this->intel['HumanErrorParentID'] = $this->intel['ErrorParentID'] ? 'Error in folder ID' : 'Perfect folder ID';
357
        $stageDBStatus = $this->intel['ErrorDBNotPresentStaging'] ? 'Is not on draft site' : ' Is on draft site';
358
        $liveDBStatus = $this->intel['ErrorDBNotPresentLive'] ? 'Is not on live site' : ' Is on live site';
359
        $this->intel['HumanErrorDBNotPresent'] = $stageDBStatus . ', ' . $liveDBStatus;
360
        $this->intel['HumanErrorInSs3Ss4Comparison'] = $this->intel['ErrorInSs3Ss4Comparison'] ?
361
            'Filename and FileFilename do not match' : 'Filename and FileFilename match';
362
        $this->intel['HumanIcon'] = File::get_icon_for_extension($this->intel['PathExtensionAsLower']);
363
    }
364
365
    protected function addCalculatedValues()
366
    {
367
        $this->intel['ErrorHasAnyError'] = false;
368
        foreach ($this->errorFields as $field) {
369
            if ($this->intel[$field]) {
370
                $this->intel['ErrorHasAnyError'] = true;
371
            }
372
        }
373
    }
374
375
    protected function getCacheKey(): string
376
    {
377
        return $this->pathHash;
378
    }
379
}
380