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

nette / assets / 14836909123

05 May 2025 12:51PM UTC coverage: 94.475% (+0.06%) from 94.413%
14836909123

push

github

Claude
assets: added new properties

2 of 2 new or added lines in 2 files covered. (100.0%)

5 existing lines in 3 files now uncovered.

171 of 181 relevant lines covered (94.48%)

0.94 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Nette\Assets;
6

7
use Nette;
8

9

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

17
        private const ExtensionToMime = [
18
                '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',
19
                'js' => 'application/javascript', 'mjs' => 'application/javascript',
20
                'css' => 'text/css',
21
                'aac' => 'audio/aac', 'flac' => 'audio/flac', 'm4a' => 'audio/mp4', 'mp3' => 'audio/mpeg', 'ogg' => 'audio/ogg', 'wav' => 'audio/wav',
22
                'avi' => 'video/x-msvideo', 'mkv' => 'video/x-matroska', 'mov' => 'video/quicktime', 'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm',
23
        ];
24

25

26
        public static function createAssetFromUrl(string $url, ?string $path, array $args = []): Asset
1✔
27
        {
28
                $args['url'] = $url;
1✔
29
                $args['sourcePath'] = $path;
1✔
30
                $mime = (string) $args['mimeType'] ??= self::guessMimeTypeFromExtension($url);
1✔
31
                $class = match (true) {
1✔
32
                        $mime === 'application/javascript' => ScriptAsset::class,
1✔
33
                        $mime === 'text/css' => StyleAsset::class,
1✔
34
                        str_starts_with($mime, 'image/') => ImageAsset::class,
1✔
35
                        str_starts_with($mime, 'audio/') => AudioAsset::class,
1✔
36
                        str_starts_with($mime, 'video/') => VideoAsset::class,
1✔
37
                        default => GenericAsset::class,
1✔
38
                };
39
                return new $class(...$args);
1✔
40
        }
41

42

43
        public static function guessMimeTypeFromExtension(string $url): ?string
1✔
44
        {
45
                return preg_match('~\.([a-z0-9]{1,5})([?#]|$)~i', $url, $m)
1✔
46
                        ? self::ExtensionToMime[strtolower($m[1])] ?? null
1✔
47
                        : null;
1✔
48
        }
49

50

51
        /**
52
         * Validates an array of options against allowed optional and required keys.
53
         * @throws \InvalidArgumentException if there are unsupported or missing options
54
         */
55
        public static function checkOptions(array $array, array $optional = [], array $required = []): void
1✔
56
        {
57
                if ($keys = array_diff(array_keys($array), $optional, $required)) {
1✔
58
                        throw new \InvalidArgumentException('Unsupported asset options: ' . implode(', ', $keys));
1✔
59
                }
60
                if ($keys = array_diff($required, array_keys($array))) {
1✔
UNCOV
61
                        throw new \InvalidArgumentException('Missing asset options: ' . implode(', ', $keys));
×
62
                }
63
        }
1✔
64

65

66
        /**
67
         * Estimates the duration (in seconds) of an MP3 file, assuming constant bitrate (CBR).
68
         * @throws \RuntimeException If the file cannot be opened, MP3 sync bits aren't found, or bitrate is invalid/unsupported.
69
         */
70
        public static function guessMP3Duration(string $path): float
1✔
71
        {
72
                if (
73
                        ($header = @file_get_contents($path, length: 10000)) === false // @ - file may not exist
1✔
74
                        || ($fileSize = @filesize($path)) === false
1✔
75
                ) {
UNCOV
76
                        throw new \RuntimeException(sprintf("Failed to open file '%s'. %s", $path, Nette\Utils\Helpers::getLastError()));
×
77
                }
78

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

84
                $frameHeader = substr($header, $frameOffset, 4);
1✔
85
                $headerBits = unpack('N', $frameHeader)[1];
1✔
86
                $bitrateIndex = ($headerBits >> 12) & 0xF;
1✔
87
                $bitrate = [null, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320][$bitrateIndex] ?? null;
1✔
88
                if ($bitrate === null) {
1✔
UNCOV
89
                        throw new \RuntimeException('Invalid or unsupported bitrate index.');
×
90
                }
91

92
                return $fileSize * 8 / $bitrate / 1000;
1✔
93
        }
94
}
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