Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
62.96% covered (warning)
62.96%
17 / 27
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Util
62.96% covered (warning)
62.96%
17 / 27
50.00% covered (danger)
50.00%
1 / 2
15.08
0.00% covered (danger)
0.00%
0 / 1
 slug
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 isAnimatedGif
41.18% covered (danger)
41.18%
7 / 17
0.00% covered (danger)
0.00%
0 / 1
16.97
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Assets;
6
7use Cosray\Exception\RuntimeException;
8use Normalizer;
9use Transliterator;
10
11class Util
12{
13    /**
14     * Conservative lowercase slug of an uploaded filename, safe as URL
15     * path segment and pool basename. May return an empty string or a
16     * bare extension for names without transliterable characters.
17     */
18    public static function slug(string $filename): string
19    {
20        $slug = Normalizer::normalize($filename, Normalizer::FORM_C) ?: $filename;
21        $latin = Transliterator::create('Any-Latin; Latin-ASCII')?->transliterate($slug);
22
23        if (is_string($latin)) {
24            $slug = $latin;
25        }
26
27        $slug = mb_strtolower($slug);
28        $slug = preg_replace('/\s+/u', '-', $slug) ?? '';
29        $slug = preg_replace('/[^a-z0-9._-]/', '', $slug) ?? '';
30        $slug = preg_replace('/-{2,}/', '-', $slug) ?? '';
31        $slug = preg_replace('/\.{2,}/', '.', $slug) ?? '';
32
33        // A leading dot survives so `Storage::key()` can keep the
34        // extension when it swaps in the uid as stem.
35        return rtrim(ltrim($slug, '-'), '-.');
36    }
37
38    public static function isAnimatedGif(string $fileName): bool
39    {
40        // Check if the file exists
41        if (!file_exists($fileName)) {
42            throw new RuntimeException('File does not exist: ' . $fileName);
43        }
44
45        // Open the file
46        $fileHandle = fopen($fileName, 'rb');
47
48        if (!$fileHandle) {
49            throw new RuntimeException('File could not be opened: ' . $fileName);
50        }
51
52        // Read the first few bytes of the file
53        $header = fread($fileHandle, 3);
54
55        // Close the file handle
56        fclose($fileHandle);
57
58        // Check if the file header matches the GIF magic number
59        if ($header === 'GIF') {
60            $fileHandle = fopen($fileName, 'rb');
61            $frameCount = 0;
62
63            while (!feof($fileHandle) && $frameCount < 2) {
64                $chunk = fread($fileHandle, 1024 * 100); // read 100kb at a time
65                $frameCount += substr_count($chunk, "\x00\x21\xF9\x04");
66
67                if ($frameCount > 1) {
68                    fclose($fileHandle);
69
70                    return true;
71                }
72            }
73        }
74
75        return false;
76    }
77}