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

timber / timber / 5690593717

pending completion
5690593717

Pull #1617

github

web-flow
Merge f587ceffa into b563d274e
Pull Request #1617: 2.x

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

3931 of 4433 relevant lines covered (88.68%)

58.28 hits per line

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

98.39
/src/ExternalImage.php
1
<?php
2

3
namespace Timber;
4

5
/**
6
 * Class ExternalImage
7
 *
8
 * The `Timber\ExternalImage` class represents an image that is not part of the WordPress content (Attachment).
9
 * Instead, it’s an image that can be either a path (relative/absolute) on the same server, or a URL (either from the
10
 * same or from a different website). When you use a URL of an image on a different website, Timber will load it into
11
 * your WordPress installation once and then load it from there.
12
 *
13
 * @api
14
 * @example
15
 * ```php
16
 * $context = Timber::context();
17
 *
18
 * // Lets say you have an external image that you want to use in your theme
19
 *
20
 * $context['cover_image'] = Timber::get_external_image($url);
21
 *
22
 * Timber::render('single.twig', $context);
23
 * ```
24
 *
25
 * ```twig
26
 * <article>
27
 *   <img src="{{ cover_image.src }}" class="cover-image" />
28
 *   <h1 class="headline">{{ post.title }}</h1>
29
 *   <div class="body">
30
 *     {{ post.content }}
31
 *   </div>
32
 * </article>
33
 * ```
34
 *
35
 * ```html
36
 * <article>
37
 *   <img src="http://example.org/wp-content/uploads/2015/06/nevermind.jpg" class="cover-image" />
38
 *   <h1 class="headline">Now you've done it!</h1>
39
 *   <div class="body">
40
 *     Whatever whatever
41
 *   </div>
42
 * </article>
43
 * ```
44
 */
45
class ExternalImage implements ImageInterface
46
{
47
    /**
48
     * Alt text.
49
     *
50
     * @api
51
     * @var string
52
     */
53
    private $alt_text;
54

55
    /**
56
     * Caption.
57
     *
58
     * @api
59
     * @var string
60
     */
61
    private $caption = '';
62

63
    /**
64
     * File.
65
     *
66
     * @api
67
     * @var mixed
68
     */
69
    public $file;
70

71
    /**
72
     * File location.
73
     *
74
     * @api
75
     * @var string The absolute path to the attachmend file in the filesystem
76
     *             (Example: `/var/www/htdocs/wp-content/themes/my-theme/images/my-pic.jpg`)
77
     */
78
    public $file_loc;
79

80
    /**
81
     * Absolute URL.
82
     *
83
     * @var string The absolute URL to the attachment.
84
     */
85
    public $abs_url;
86

87
    /**
88
     * File extension.
89
     *
90
     * @api
91
     * @since 2.0.0
92
     * @var null|string A file extension.
93
     */
94
    public $file_extension = null;
95

96
    /**
97
     * Formatted file size.
98
     *
99
     * @api
100
     * @since 2.0.0
101
     * @var FileSize File size string.
102
     */
103
    private FileSize $file_size;
104

105
    /**
106
     * File types.
107
     *
108
     * @var array An array of supported relative file types.
109
     */
110
    private $image_file_types = [
111
        'jpg',
112
        'jpeg',
113
        'png',
114
        'svg',
115
        'bmp',
116
        'ico',
117
        'gif',
118
        'tiff',
119
        'pdf',
120
    ];
121

122
    /**
123
     * Image dimensions.
124
     *
125
     * @internal
126
     * @var ImageDimensions|null stores Image Dimensions in a structured way.
127
     */
128
    protected ?ImageDimensions $image_dimensions;
129

130
    /**
131
     * Inits the ExternalImage object.
132
     *
133
     * @internal
134
     * @param $url string URL or path to load the image from.
135
     * @param $args array An array of arguments for the image.
136
     */
137
    public static function build($url, array $args = [])
138
    {
139
        if (!\is_string($url) || \is_numeric($url)) {
16✔
140
            return null;
1✔
141
        }
142

143
        $args = \wp_parse_args($args, [
15✔
144
            'alt' => '',
15✔
145
        ]);
15✔
146

147
        $external_image = new static();
15✔
148

149
        if (!empty($args['alt'])) {
15✔
150
            $external_image->set_alt($args['alt']);
1✔
151
        }
152

153
        if (!empty($args['caption'])) {
15✔
154
            $external_image->set_caption($args['caption']);
1✔
155
        }
156

157
        if (\strstr($url, '://')) {
15✔
158
            // Assume URL.
159
            $external_image->init_with_url($url);
2✔
160

161
            return $external_image;
2✔
162
        } elseif (\strstr($url, ABSPATH)) {
13✔
163
            // Assume absolute path.
164
            $external_image->init_with_file_path($url);
12✔
165

166
            return $external_image;
12✔
167
        } else {
168
            // Check for image file types.
169
            foreach ($external_image->image_file_types as $type) {
1✔
170
                // Assume a relative path.
171
                if (\strstr(\strtolower($url), $type)) {
1✔
172
                    $external_image->init_with_relative_path($url);
1✔
173

174
                    return $external_image;
1✔
175
                }
176
            }
177
        }
178

179
        return null;
×
180
    }
181

182
    /**
183
     * Gets the source URL for the image.
184
     *
185
     * @api
186
     * @example
187
     * ```twig
188
     * <img src="{{ post.thumbnail.src }}">
189
     * <img src="{{ post.thumbnail.src('medium') }}">
190
     * ```
191
     * ```html
192
     * <img src="http://example.org/wp-content/uploads/2015/08/pic.jpg" />
193
     * <img src="http://example.org/wp-content/uploads/2015/08/pic-800-600.jpg">
194
     * ```
195
     *
196
     * @param string $size Ignored. For compatibility with Timber\Image.
197
     *
198
     * @return string The src URL for the image.
199
     */
200
    public function src($size = 'full')
201
    {
202
        return URLHelper::maybe_secure_url($this->abs_url);
7✔
203
    }
204

205
    /**
206
     * Gets the relative path to an attachment.
207
     *
208
     * @api
209
     * @example
210
     * ```twig
211
     * <img src="{{ image.path }}" />
212
     * ```
213
     * ```html
214
     * <img src="/wp-content/uploads/2015/08/pic.jpg" />
215
     * ```
216
     *
217
     * @return string The relative path to an attachment.
218
     */
219
    public function path(): string
220
    {
221
        return URLHelper::get_rel_path($this->file);
1✔
222
    }
223

224
    /**
225
     * Gets filesize in a human-readable format.
226
     *
227
     * This can be useful if you want to display the human-readable filesize for a file. It’s
228
     * easier to read «16 KB» than «16555 bytes» or «1 MB» than «1048576 bytes».
229
     *
230
     * @api
231
     * @since 2.0.0
232
     * @example
233
     * Use filesize information in a link that downloads a file:
234
     *
235
     * ```twig
236
     * <a class="download" href="{{ attachment.src }}" download="{{ attachment.title }}">
237
     *     <span class="download-title">{{ attachment.title }}</span>
238
     *     <span class="download-info">(Download, {{ attachment.size }})</span>
239
     * </a>
240
     * ```
241
     *
242
     * @return string The filesize string in a human-readable format or null if the
243
     *                filesize can’t be read.
244
     */
245
    public function size(): string
246
    {
247
        return $this->file_size->size();
1✔
248
    }
249

250
    /**
251
     * Gets filesize in bytes.
252
     *
253
     * @api
254
     * @since 2.0.0
255
     * @example
256
     *
257
     * ```twig
258
     * <table>
259
     *     {% for attachment in Attachment(attachment_ids) %}
260
     *         <tr>
261
     *             <td>{{ attachment.title }}</td>
262
     *             <td>{{ attachment.extension }}</td>
263
     *             <td>{{ attachment.size_raw }} bytes</td>
264
     *         </tr>
265
     *     {% endfor %}
266
     * </table>
267
     * ```
268
     *
269
     * @return int|null The filesize number in bytes, or null if the filesize can’t be read.
270
     */
271
    public function size_raw()
272
    {
273
        return $this->file_size->size_raw();
1✔
274
    }
275

276
    /**
277
     * Gets the src for an attachment.
278
     *
279
     * @api
280
     *
281
     * @return string The src of the attachment.
282
     */
283
    public function __toString()
284
    {
285
        return $this->src();
1✔
286
    }
287

288
    /**
289
     * Gets the extension of the attached file.
290
     *
291
     * @api
292
     * @since 2.0.0
293
     * @example
294
     *
295
     * Use extension information in a link that downloads a file:
296
     *
297
     * ```twig
298
     * <a class="download" href="{{ attachment.src }}" download="{{ attachment.title }}">
299
     *     <span class="download-title">{{ attachment.title }}</span>
300
     *     <span class="download-info">
301
     *         (Download {{ attachment.extension|upper }}, {{ attachment.size }})
302
     *     </span>
303
     * </a>
304
     * ```
305
     *
306
     * @return string|null An uppercase extension string.
307
     */
308
    public function extension(): ?string
309
    {
310
        if (!$this->file_extension) {
1✔
311
            $file_info = \wp_check_filetype($this->file);
1✔
312

313
            if (!empty($file_info['ext'])) {
1✔
314
                $this->file_extension = \strtoupper($file_info['ext']);
1✔
315
            }
316
        }
317

318
        return $this->file_extension;
1✔
319
    }
320

321
    /**
322
     * Gets the width of the image in pixels.
323
     *
324
     * @api
325
     * @example
326
     * ```twig
327
     * <img src="{{ image.src }}" width="{{ image.width }}" />
328
     * ```
329
     * ```html
330
     * <img src="http://example.org/wp-content/uploads/2015/08/pic.jpg" width="1600" />
331
     * ```
332
     *
333
     * @return int|null The width of the image in pixels. Null if the width can’t be read, e.g. because the file doesn’t
334
     *                  exist.
335
     */
336
    public function width(): ?int
337
    {
338
        return $this->image_dimensions->width();
4✔
339
    }
340

341
    /**
342
     * Gets the height of the image in pixels.
343
     *
344
     * @api
345
     * @example
346
     * ```twig
347
     * <img src="{{ image.src }}" height="{{ image.height }}" />
348
     * ```
349
     * ```html
350
     * <img src="http://example.org/wp-content/uploads/2015/08/pic.jpg" height="900" />
351
     * ```
352
     *
353
     * @return int|null The height of the image in pixels. Null if the height can’t be read, e.g. because the file
354
     *                  doesn’t exist.
355
     */
356
    public function height(): ?int
357
    {
358
        return $this->image_dimensions->height();
4✔
359
    }
360

361
    /**
362
     * Gets the aspect ratio of the image.
363
     *
364
     * @api
365
     * @example
366
     * ```twig
367
     * {% if post.thumbnail.aspect < 1 %}
368
     *     {# handle vertical image #}
369
     *     <img src="{{ post.thumbnail.src|resize(300, 500) }}" alt="A basketball player" />
370
     * {% else %}
371
     *     <img src="{{ post.thumbnail.src|resize(500) }}" alt="A sumo wrestler" />
372
     * {% endif %}
373
     * ```
374
     *
375
     * @return float The aspect ratio of the image.
376
     */
377
    public function aspect()
378
    {
379
        return $this->image_dimensions->aspect();
4✔
380
    }
381

382
    /**
383
     * Sets the relative alt text of the image.
384
     *
385
     * @param string $alt Alt text for the image.
386
     */
387
    public function set_alt(string $alt)
388
    {
389
        $this->alt_text = $alt;
1✔
390
    }
391

392
    /**
393
     * Sets the relative alt text of the image.
394
     *
395
     * @param string $caption Caption text for the image
396
     */
397
    public function set_caption(string $caption)
398
    {
399
        $this->caption = $caption;
1✔
400
    }
401

402
    /**
403
     * Inits the object with an absolute path.
404
     *
405
     * @internal
406
     *
407
     * @param string $file_path An absolute path to a file.
408
     */
409
    protected function init_with_file_path($file_path)
410
    {
411
        $url = URLHelper::file_system_to_url($file_path);
12✔
412

413
        $this->abs_url = $url;
12✔
414
        $this->file_loc = $file_path;
12✔
415
        $this->file = $file_path;
12✔
416
        $this->image_dimensions = new ImageDimensions($file_path);
12✔
417
        $this->file_size = new FileSize($file_path);
12✔
418
    }
419

420
    /**
421
     * Inits the object with a relative path.
422
     *
423
     * @internal
424
     *
425
     * @param string $relative_path A relative path to a file.
426
     */
427
    protected function init_with_relative_path($relative_path)
428
    {
429
        $file_path = URLHelper::get_full_path($relative_path);
1✔
430

431
        $this->abs_url = \home_url($relative_path);
1✔
432
        $this->file_loc = $file_path;
1✔
433
        $this->file = $file_path;
1✔
434
        $this->image_dimensions = new ImageDimensions($file_path);
1✔
435
        $this->file_size = new FileSize($file_path);
1✔
436
    }
437

438
    /**
439
     * Inits the object with an URL.
440
     *
441
     * @internal
442
     *
443
     * @param string $url An URL on the same host.
444
     */
445
    protected function init_with_url($url)
446
    {
447
        if (!URLHelper::is_local($url)) {
2✔
448
            $url = ImageHelper::sideload_image($url);
1✔
449
        }
450

451
        $this->abs_url = $url;
2✔
452

453
        if (URLHelper::is_local($url)) {
2✔
454
            $this->file = URLHelper::remove_double_slashes(
2✔
455
                ABSPATH . URLHelper::get_rel_url($url)
2✔
456
            );
2✔
457
            $this->file_loc = URLHelper::remove_double_slashes(
2✔
458
                ABSPATH . URLHelper::get_rel_url($url)
2✔
459
            );
2✔
460
            $this->image_dimensions = new ImageDimensions($this->file_loc);
2✔
461
            $this->file_size = new FileSize($this->file_loc);
2✔
462
        }
463
    }
464

465
    /**
466
     * Gets the alt text for an image.
467
     *
468
     * For better accessibility, you should always add an alt attribute to your images, even if it’s
469
     * empty.
470
     *
471
     * @api
472
     * @example
473
     * ```twig
474
     * <img src="{{ image.src }}" alt="{{ image.alt }}" />
475
     * ```
476
     * ```html
477
     * <img
478
     *     src="http://example.org/wp-content/uploads/2015/08/pic.jpg"
479
     *     alt="You should always add alt texts to your images for better accessibility"
480
     * />
481
     * ```
482
     *
483
     * @return string Alt text stored in WordPress.
484
     */
485
    public function alt(): string
486
    {
487
        return $this->alt_text;
1✔
488
    }
489

490
    public function caption(): string
491
    {
492
        return $this->caption;
1✔
493
    }
494
}
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