• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / assets / 20835381747

08 Jan 2026 11:25PM UTC coverage: 94.048%. Remained the same
20835381747

push

github

dg
added CLAUDE.md

474 of 504 relevant lines covered (94.05%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.25
/src/Assets/Helpers.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Nette\Assets;
6

7
use Nette;
8
use function array_diff, array_keys, count, explode, file_get_contents, filesize, implode, json_decode, parse_url, preg_match, sprintf, strpos, strtolower, substr, unpack;
9

10

11
/**
12
 * Static helper class providing utility functions for working with assets.
13
 */
14
final class Helpers
15
{
16
        use Nette\StaticClass;
17

18
        private const ExtensionToMime = [
19
                'avif' => 'image/avif', 'gif' => 'image/gif', 'ico' => 'image/vnd.microsoft.icon', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'png' => 'image/png', 'svg' => 'image/svg+xml', 'webp' => 'image/webp',
20
                'js' => 'application/javascript', 'mjs' => 'application/javascript',
21
                'css' => 'text/css',
22
                'aac' => 'audio/aac', 'flac' => 'audio/flac', 'm4a' => 'audio/mp4', 'mp3' => 'audio/mpeg', 'ogg' => 'audio/ogg', 'wav' => 'audio/wav',
23
                'avi' => 'video/x-msvideo', 'mkv' => 'video/x-matroska', 'mov' => 'video/quicktime', 'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm',
24
                'woff' => 'font/woff', 'woff2' => 'font/woff2', 'ttf' => 'font/ttf',
25
        ];
26

27

28
        /**
29
         * Creates an Asset instance. The asset type is detected by 'mimeType' if provided in $args,
30
         * otherwise is guessed from the file extension of $path or $url.
31
         * @param  array<string, mixed>  $args parameters passed to the asset constructor
32
         */
33
        public static function createAssetFromUrl(string $url, ?string $path = null, array $args = []): Asset
1✔
34
        {
35
                $args['url'] = $url;
1✔
36
                $args['file'] = $path;
1✔
37
                $argsMime = $args;
1✔
38
                $mimeType = (string) $argsMime['mimeType'] ??= self::guessMimeTypeFromExtension($path ?? $url);
1✔
39
                $primary = explode('/', $mimeType, 2)[0];
1✔
40
                return match (true) {
41
                        $mimeType === 'application/javascript' => new ScriptAsset(...$args),
1✔
42
                        $mimeType === 'text/css' => new StyleAsset(...$args),
1✔
43
                        $primary === 'image' => new ImageAsset(...$args),
1✔
44
                        $primary === 'audio' => new AudioAsset(...$argsMime),
1✔
45
                        $primary === 'video' => new VideoAsset(...$argsMime),
1✔
46
                        $primary === 'font' => new FontAsset(...$argsMime),
1✔
47
                        default => new GenericAsset(...$argsMime),
1✔
48
                };
49
        }
50

51

52
        public static function guessMimeTypeFromExtension(string $url): ?string
1✔
53
        {
54
                return preg_match('~\.([a-z0-9]{1,5})([?#]|$)~i', $url, $m)
1✔
55
                        ? self::ExtensionToMime[strtolower($m[1])] ?? null
1✔
56
                        : null;
1✔
57
        }
58

59

60
        /**
61
         * Splits a potentially qualified reference 'mapper:reference' into a [mapper, reference] array.
62
         * @return array{?string, string}
63
         */
64
        public static function parseReference(string $qualifiedRef): array
1✔
65
        {
66
                $parts = explode(':', $qualifiedRef, 2);
1✔
67
                return count($parts) === 1
1✔
68
                        ? [null, $parts[0]]
1✔
69
                        : [$parts[0], $parts[1]];
1✔
70
        }
71

72

73
        /**
74
         * Validates an array of options against allowed optional and required keys.
75
         * @param  array<string, mixed>  $array
76
         * @param  list<string> $optional
77
         * @param  list<string>  $required
78
         * @throws \InvalidArgumentException if there are unsupported or missing options
79
         */
80
        public static function checkOptions(array $array, array $optional = [], array $required = []): void
1✔
81
        {
82
                if ($keys = array_diff(array_keys($array), $optional, $required)) {
1✔
83
                        throw new \InvalidArgumentException('Unsupported asset options: ' . implode(', ', $keys));
1✔
84
                }
85
                if ($keys = array_diff($required, array_keys($array))) {
1✔
86
                        throw new \InvalidArgumentException('Missing asset options: ' . implode(', ', $keys));
×
87
                }
88
        }
1✔
89

90

91
        /**
92
         * Estimates the duration (in seconds) of an MP3 file, assuming constant bitrate (CBR).
93
         * @throws \RuntimeException If the file cannot be opened, MP3 sync bits aren't found, or the bitrate is invalid/unsupported.
94
         */
95
        public static function guessMP3Duration(string $path): float
1✔
96
        {
97
                if (
98
                        ($header = @file_get_contents($path, length: 10000)) === false // @ - file may not exist
1✔
99
                        || ($fileSize = @filesize($path)) === false
1✔
100
                ) {
101
                        throw new \RuntimeException(sprintf("Failed to open file '%s'. %s", $path, Nette\Utils\Helpers::getLastError()));
×
102
                }
103

104
                $frameOffset = strpos($header, "\xFF\xFB"); // 0xFB indicates MPEG Version 1, Layer III, no protection bit.
1✔
105
                if ($frameOffset === false) {
1✔
106
                        throw new \RuntimeException('Failed to find MP3 frame sync bits.');
1✔
107
                }
108

109
                $frameHeader = substr($header, $frameOffset, 4);
1✔
110
                $headerBits = unpack('N', $frameHeader)[1];
1✔
111
                $bitrateIndex = ($headerBits >> 12) & 0xF;
1✔
112
                $bitrate = [null, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320][$bitrateIndex] ?? null;
1✔
113
                if ($bitrate === null) {
1✔
114
                        throw new \RuntimeException('Invalid or unsupported bitrate index.');
×
115
                }
116

117
                return $fileSize * 8 / $bitrate / 1000;
1✔
118
        }
119

120

121
        public static function detectDevServer(string $infoFile): ?string
122
        {
123
                return ($info = @file_get_contents($infoFile)) // @ file may not exists
×
124
                        && ($info = json_decode($info, associative: true))
×
125
                        && isset($info['devServer'])
×
126
                        && ($url = parse_url($info['devServer']))
×
127
                        ? $info['devServer']
×
128
                        : null;
×
129
        }
130
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc