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

dg / texy / 12879605443

21 Jan 2025 03:31AM UTC coverage: 92.224% (+0.03%) from 92.197%
12879605443

push

github

dg
regexp: uses unmatched as null (BC break)

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

101 existing lines in 14 files now uncovered.

2372 of 2572 relevant lines covered (92.22%)

0.92 hits per line

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

82.61
/src/Texy/Modules/ImageModule.php
1
<?php
2

3
/**
4
 * This file is part of the Texy! (https://texy.info)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Texy\Modules;
11

12
use Texy;
13
use Texy\Helpers;
14
use Texy\Image;
15
use Texy\Patterns;
16

17

18
/**
19
 * Images module.
20
 */
21
final class ImageModule extends Texy\Module
22
{
23
        /** root of relative images (http) */
24
        public ?string $root = 'images/';
25

26
        /** root of linked images (http) */
27
        public ?string $linkedRoot = 'images/';
28

29
        /** physical location of images on server */
30
        public ?string $fileRoot = null;
31

32
        /** left-floated images CSS class */
33
        public ?string $leftClass = null;
34

35
        /** right-floated images CSS class */
36
        public ?string $rightClass = null;
37

38
        /** default alternative text */
39
        public ?string $defaultAlt = '';
40

41
        /** @var array<string, Image> image references */
42
        private array $references = [];
43

44

45
        public function __construct(Texy\Texy $texy)
1✔
46
        {
47
                $this->texy = $texy;
1✔
48

49
                $texy->allowed['image/definition'] = true;
1✔
50
                $texy->addHandler('image', $this->solve(...));
1✔
51
                $texy->addHandler('beforeParse', $this->beforeParse(...));
1✔
52

53
                // [*image*]:LINK
54
                $texy->registerLinePattern(
1✔
55
                        $this->patternImage(...),
1✔
56
                        '~
57
                                \[\* \ *+                         # opening bracket with asterisk
58
                                ([^\n' . Patterns::MARK . ']{1,1000}) # URLs (1)
59
                                ' . Patterns::MODIFIER . '?       # modifier (2)
60
                                \ *+
61
                                ( \* | (?<! < ) > | < )           # alignment (3)
62
                                \]
63
                                (?:
64
                                        :(' . Patterns::LINK_URL . ' | : ) # link or just colon (4)
1✔
65
                                )??
66
                        ~U',
67
                        'image',
1✔
68
                );
69

70
        }
1✔
71

72

73
        /**
74
         * Text pre-processing.
75
         */
76
        private function beforeParse(Texy\Texy $texy, &$text): void
1✔
77
        {
78
                if (!empty($texy->allowed['image/definition'])) {
1✔
79
                        // [*image*]: urls .(title)[class]{style}
80
                        $text = Texy\Regexp::replace(
1✔
81
                                $text,
1✔
82
                                '~^
83
                                        \[\*                              # opening [*
84
                                        ( [^\n]{1,100} )                  # reference (1)
85
                                        \*\]                              # closing *]
86
                                        : [\ \t]+
87
                                        (.{1,1000})                       # URL (2)
88
                                        [\ \t]*
89
                                        ' . Patterns::MODIFIER . '?       # modifier (3)
1✔
90
                                        \s*
91
                                $~mU',
92
                                $this->patternReferenceDef(...),
1✔
93
                        );
94
                }
95
        }
1✔
96

97

98
        /**
99
         * Callback for: [*image*]: urls .(title)[class]{style}.
100
         */
101
        private function patternReferenceDef(array $matches): string
1✔
102
        {
103
                [, $mRef, $mURLs, $mMod] = $matches;
1✔
104
                // [1] => [* (reference) *]
105
                // [2] => urls
106
                // [3] => .(title)[class]{style}<>
107

108
                $image = $this->factoryImage($mURLs, $mMod, false);
1✔
109
                $this->addReference($mRef, $image);
1✔
110
                return '';
1✔
111
        }
112

113

114
        /**
115
         * Callback for [* small.jpg 80x13 || big.jpg .(alternative text)[class]{style}>]:LINK.
116
         */
117
        public function patternImage(Texy\LineParser $parser, array $matches): Texy\HtmlElement|string|null
1✔
118
        {
119
                [, $mURLs, $mMod, $mAlign, $mLink] = $matches;
1✔
120
                // [1] => URLs
121
                // [2] => .(title)[class]{style}<>
122
                // [3] => * < >
123
                // [4] => url | [ref] | [*image*]
124

125
                $image = $this->factoryImage($mURLs, $mMod . $mAlign);
1✔
126

127
                if ($mLink) {
1✔
128
                        if ($mLink === ':') {
1✔
129
                                $link = new Texy\Link($image->linkedURL ?? $image->URL);
1✔
130
                                $link->raw = ':';
1✔
131
                                $link->type = $link::IMAGE;
1✔
132
                        } else {
133
                                $link = $this->texy->linkModule->factoryLink($mLink, null, null);
1✔
134
                        }
135
                } else {
136
                        $link = null;
1✔
137
                }
138

139
                return $this->texy->invokeAroundHandlers('image', $parser, [$image, $link]);
1✔
140
        }
141

142

143
        /**
144
         * Adds new named reference to image.
145
         */
146
        public function addReference(string $name, Image $image): void
1✔
147
        {
148
                $image->name = Helpers::toLower($name);
1✔
149
                $this->references[$image->name] = $image;
1✔
150
        }
1✔
151

152

153
        /**
154
         * Returns named reference.
155
         */
156
        public function getReference(string $name): ?Image
1✔
157
        {
158
                $name = Helpers::toLower($name);
1✔
159
                if (isset($this->references[$name])) {
1✔
160
                        return clone $this->references[$name];
1✔
161
                }
162

163
                return null;
1✔
164
        }
165

166

167
        /**
168
         * Parses image's syntax. Input: small.jpg 80x13 || linked.jpg
169
         */
170
        public function factoryImage(string $content, ?string $mod, bool $tryRef = true): Image
1✔
171
        {
172
                $image = $tryRef ? $this->getReference(trim($content)) : null;
1✔
173

174
                if (!$image) {
1✔
175
                        $texy = $this->texy;
1✔
176
                        $content = explode('|', $content);
1✔
177
                        $image = new Image;
1✔
178

179
                        // dimensions
180
                        $matches = null;
1✔
181
                        if ($matches = Texy\Regexp::match($content[0], '~^(.*)\ (\d+|\?)\ *(X|x)\ *(\d+|\?)\ *$~U')) {
1✔
182
                                $image->URL = trim($matches[1]);
1✔
183
                                $image->asMax = $matches[3] === 'X';
1✔
184
                                $image->width = $matches[2] === '?' ? null : (int) $matches[2];
1✔
185
                                $image->height = $matches[4] === '?' ? null : (int) $matches[4];
1✔
186
                        } else {
187
                                $image->URL = trim($content[0]);
1✔
188
                        }
189

190
                        if (!$texy->checkURL($image->URL, $texy::FILTER_IMAGE)) {
1✔
UNCOV
191
                                $image->URL = null;
×
192
                        }
193

194
                        // linked image
195
                        if (isset($content[2])) {
1✔
UNCOV
196
                                $tmp = trim($content[2]);
×
UNCOV
197
                                if ($tmp !== '' && $texy->checkURL($tmp, $texy::FILTER_ANCHOR)) {
×
UNCOV
198
                                        $image->linkedURL = $tmp;
×
199
                                }
200
                        }
201
                }
202

203
                $image->modifier->setProperties($mod);
1✔
204
                return $image;
1✔
205
        }
206

207

208
        /**
209
         * Finish invocation.
210
         */
211
        public function solve(
1✔
212
                ?Texy\HandlerInvocation $invocation,
213
                Image $image,
214
                ?Texy\Link $link = null,
215
        ): ?Texy\HtmlElement
216
        {
217
                if ($image->URL == null) {
1✔
UNCOV
218
                        return null;
×
219
                }
220

221
                $texy = $this->texy;
1✔
222

223
                $mod = $image->modifier;
1✔
224
                $alt = $mod->title;
1✔
225
                $mod->title = null;
1✔
226
                $hAlign = $mod->hAlign;
1✔
227
                $mod->hAlign = null;
1✔
228

229
                $el = new Texy\HtmlElement('img');
1✔
230
                $el->attrs['src'] = null; // trick - move to front
1✔
231
                $mod->decorate($texy, $el);
1✔
232
                $el->attrs['src'] = Helpers::prependRoot($image->URL, $this->root);
1✔
233
                if (!isset($el->attrs['alt'])) {
1✔
234
                        $el->attrs['alt'] = $alt === null
1✔
235
                                ? $this->defaultAlt
1✔
236
                                : $texy->typographyModule->postLine($alt);
1✔
237
                }
238

239
                if ($hAlign) {
1✔
240
                        $var = $hAlign . 'Class'; // leftClass, rightClass
1✔
241
                        if (!empty($this->$var)) {
1✔
242
                                $el->attrs['class'][] = $this->$var;
1✔
243

244
                        } elseif (empty($texy->alignClasses[$hAlign])) {
1✔
245
                                $el->attrs['style']['float'] = $hAlign;
1✔
246

247
                        } else {
UNCOV
248
                                $el->attrs['class'][] = $texy->alignClasses[$hAlign];
×
249
                        }
250
                }
251

252
                if (!is_int($image->width) || !is_int($image->height) || $image->asMax) {
1✔
253
                        $this->detectDimensions($image);
1✔
254
                }
255

256
                $el->attrs['width'] = $image->width;
1✔
257
                $el->attrs['height'] = $image->height;
1✔
258

259
                $texy->summary['images'][] = $el->attrs['src'];
1✔
260

261
                if ($link) {
1✔
262
                        return $texy->linkModule->solve(null, $link, $el);
1✔
263
                }
264

265
                return $el;
1✔
266
        }
267

268

269
        private function detectDimensions(Image $image): void
1✔
270
        {
271
                // absolute URL & security check for double dot
272
                if (!Helpers::isRelative($image->URL) || str_contains($image->URL, '..')) {
1✔
273
                        return;
1✔
274
                }
275

276
                $file = rtrim((string) $this->fileRoot, '/\\') . '/' . $image->URL;
1✔
277
                if (!@is_file($file) || !($size = @getimagesize($file))) { // intentionally @
1✔
278
                        return;
1✔
279
                }
280

UNCOV
281
                if ($image->asMax) {
×
282
                        $ratio = 1;
×
283
                        if (is_int($image->width)) {
×
UNCOV
284
                                $ratio = min($ratio, $image->width / $size[0]);
×
285
                        }
286

UNCOV
287
                        if (is_int($image->height)) {
×
UNCOV
288
                                $ratio = min($ratio, $image->height / $size[1]);
×
289
                        }
290

UNCOV
291
                        $image->width = (int) round($ratio * $size[0]);
×
UNCOV
292
                        $image->height = (int) round($ratio * $size[1]);
×
293

UNCOV
294
                } elseif (is_int($image->width)) {
×
UNCOV
295
                        $image->height = (int) round($size[1] / $size[0] * $image->width);
×
296

UNCOV
297
                } elseif (is_int($image->height)) {
×
UNCOV
298
                        $image->width = (int) round($size[0] / $size[1] * $image->height);
×
299

300
                } else {
UNCOV
301
                        $image->width = $size[0];
×
UNCOV
302
                        $image->height = $size[1];
×
303
                }
304
        }
305
}
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