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

timber / timber / 20846050397

09 Jan 2026 08:35AM UTC coverage: 89.608%. Remained the same
20846050397

Pull #3181

travis-ci

web-flow
Merge 5bae5023f into 6ae23b1a2
Pull Request #3181: test: Fix issue with image test that sets a constant

4596 of 5129 relevant lines covered (89.61%)

63.8 hits per line

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

96.85
/src/Twig.php
1
<?php
2

3
namespace Timber;
4

5
use DateInterval;
6
use DateTime;
7
use DateTimeInterface;
8
use Exception;
9
use Timber\Factory\PostFactory;
10
use Timber\Factory\TermFactory;
11
use Twig\DeprecatedCallableInfo;
12
use Twig\Environment;
13
use Twig\Error\RuntimeError;
14
use Twig\Extension\CoreExtension;
15
use Twig\Extension\EscaperExtension;
16
use Twig\Runtime\EscaperRuntime;
17
use Twig\TwigFilter;
18
use Twig\TwigFunction;
19

20
/**
21
 * Class Twig
22
 */
23
class Twig
24
{
25
    public static $dir_name;
26

27
    /**
28
     * @codeCoverageIgnore
29
     */
30
    public static function init(): void
31
    {
32
        $self = new self();
33

34
        \add_filter('timber/twig', [$self, 'add_timber_functions']);
35
        \add_filter('timber/twig', [$self, 'add_timber_filters']);
36
        \add_filter('timber/twig', [$self, 'add_timber_escaper_filters']);
37
        \add_filter('timber/twig', [$self, 'add_timber_escapers']);
38
        \add_filter('timber/loader/twig', [$self, 'set_defaults']);
39
    }
40

41
    /**
42
     * Get Timber default functions
43
     *
44
     * @return array Default Timber functions
45
     */
46
    public function get_timber_functions()
415✔
47
    {
48
        $post_factory = new PostFactory();
415✔
49
        $termFactory = new TermFactory();
415✔
50

51
        $functions = [
415✔
52
            'action' => [
415✔
53
                'callable' => function ($action_name, ...$args) {
415✔
54
                    \do_action_ref_array($action_name, $args);
1✔
55
                },
415✔
56
            ],
415✔
57
            'function' => [
415✔
58
                'callable' => [$this, 'exec_function'],
415✔
59
            ],
415✔
60
            'fn' => [
415✔
61
                'callable' => [$this, 'exec_function'],
415✔
62
            ],
415✔
63
            'get_post' => [
415✔
64
                'callable' => [Timber::class, 'get_post'],
415✔
65
            ],
415✔
66
            'get_image' => [
415✔
67
                'callable' => [Timber::class, 'get_image'],
415✔
68
            ],
415✔
69
            'get_external_image' => [
415✔
70
                'callable' => [Timber::class, 'get_external_image'],
415✔
71
            ],
415✔
72
            'get_attachment' => [
415✔
73
                'callable' => [Timber::class, 'get_attachment'],
415✔
74
            ],
415✔
75
            'get_posts' => [
415✔
76
                'callable' => [Timber::class, 'get_posts'],
415✔
77
            ],
415✔
78
            'get_attachment_by' => [
415✔
79
                'callable' => [Timber::class, 'get_attachment_by'],
415✔
80
            ],
415✔
81
            'get_term' => [
415✔
82
                'callable' => [Timber::class, 'get_term'],
415✔
83
            ],
415✔
84
            'get_terms' => [
415✔
85
                'callable' => [Timber::class, 'get_terms'],
415✔
86
            ],
415✔
87
            'get_user' => [
415✔
88
                'callable' => [Timber::class, 'get_user'],
415✔
89
            ],
415✔
90
            'get_users' => [
415✔
91
                'callable' => [Timber::class, 'get_users'],
415✔
92
            ],
415✔
93
            'get_comment' => [
415✔
94
                'callable' => [Timber::class, 'get_comment'],
415✔
95
            ],
415✔
96
            'get_comments' => [
415✔
97
                'callable' => [Timber::class, 'get_comments'],
415✔
98
            ],
415✔
99
            'Post' => [
415✔
100
                'callable' => function ($post_id) use ($post_factory) {
415✔
101
                    Helper::deprecated('{{ Post() }}', '{{ get_post() }} or {{ get_posts() }}', '2.0.0');
2✔
102
                    return $post_factory->from($post_id);
2✔
103
                },
415✔
104
                'options' => [
415✔
105
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
106
                        package: 'timber/timber',
415✔
107
                        version: '2.0.0',
415✔
108
                        altName: '{{ get_post() }} or {{ get_posts() }}',
415✔
109
                        altPackage: null,
415✔
110
                        altVersion: null
415✔
111
                    ),
415✔
112
                ],
415✔
113
            ],
415✔
114
            'TimberPost' => [
415✔
115
                'callable' => function ($post_id) use ($post_factory) {
415✔
116
                    Helper::deprecated('{{ TimberPost() }}', '{{ get_post() }} or {{ get_posts() }}', '2.0.0');
2✔
117
                    return $post_factory->from($post_id);
2✔
118
                },
415✔
119
                'options' => [
415✔
120
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
121
                        package: 'timber/timber',
415✔
122
                        version: '2.0.0',
415✔
123
                        altName: '{{ get_post() }} or {{ get_posts() }}',
415✔
124
                        altPackage: null,
415✔
125
                        altVersion: null
415✔
126
                    ),
415✔
127
                ],
415✔
128
            ],
415✔
129
            'Image' => [
415✔
130
                'callable' => function ($post_id) use ($post_factory) {
415✔
131
                    Helper::deprecated('{{ Image() }}', '{{ get_image() }}, {{ get_post() }}, {{ get_posts() }}, {{ get_attachment() }} or {{ get_attachment_by() }}', '2.0.0');
2✔
132
                    return $post_factory->from($post_id);
2✔
133
                },
415✔
134
                'options' => [
415✔
135
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
136
                        package: 'timber/timber',
415✔
137
                        version: '2.0.0',
415✔
138
                        altName: '{{ get_image() }}',
415✔
139
                        altPackage: null,
415✔
140
                        altVersion: null
415✔
141
                    ),
415✔
142
                ],
415✔
143
            ],
415✔
144
            'TimberImage' => [
415✔
145
                'callable' => function ($post_id) use ($post_factory) {
415✔
146
                    Helper::deprecated('{{ TimberImage() }}', '{{ get_image() }}, {{ get_post() }}, {{ get_posts() }}, {{ get_attachment() }} or {{ get_attachment_by() }}', '2.0.0');
2✔
147
                    return $post_factory->from($post_id);
2✔
148
                },
415✔
149
                'options' => [
415✔
150
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
151
                        package: 'timber/timber',
415✔
152
                        version: '2.0.0',
415✔
153
                        altName: '{{ get_image() }}',
415✔
154
                        altPackage: null,
415✔
155
                        altVersion: null
415✔
156
                    ),
415✔
157
                ],
415✔
158
            ],
415✔
159
            'Term' => [
415✔
160
                'callable' => function ($term_id) use ($termFactory) {
415✔
161
                    Helper::deprecated('{{ Term() }}', '{{ get_term() }} or {{ get_terms() }}', '2.0.0');
2✔
162
                    return $termFactory->from($term_id);
2✔
163
                },
415✔
164
                'options' => [
415✔
165
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
166
                        package: 'timber/timber',
415✔
167
                        version: '2.0.0',
415✔
168
                        altName: '{{ get_term() }}',
415✔
169
                        altPackage: null,
415✔
170
                        altVersion: null
415✔
171
                    ),
415✔
172
                ],
415✔
173
            ],
415✔
174
            'TimberTerm' => [
415✔
175
                'callable' => function ($term_id) use ($termFactory) {
415✔
176
                    Helper::deprecated('{{ TimberTerm() }}', '{{ get_term() }} or {{ get_terms() }}', '2.0.0');
2✔
177
                    return $termFactory->from($term_id);
2✔
178
                },
415✔
179
                'options' => [
415✔
180
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
181
                        package: 'timber/timber',
415✔
182
                        version: '2.0.0',
415✔
183
                        altName: '{{ get_term() }} or {{ get_terms() }}',
415✔
184
                        altPackage: null,
415✔
185
                        altVersion: null
415✔
186
                    ),
415✔
187
                ],
415✔
188
            ],
415✔
189
            'User' => [
415✔
190
                'callable' => function ($user_id) {
415✔
191
                    Helper::deprecated('{{ User() }}', '{{ get_user() }} or {{ get_users() }}', '2.0.0');
2✔
192
                    return Timber::get_user($user_id);
2✔
193
                },
415✔
194
                'options' => [
415✔
195
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
196
                        package: 'timber/timber',
415✔
197
                        version: '2.0.0',
415✔
198
                        altName: '{{ get_user() }} or {{ get_users() }}',
415✔
199
                        altPackage: null,
415✔
200
                        altVersion: null
415✔
201
                    ),
415✔
202
                ],
415✔
203
            ],
415✔
204
            'TimberUser' => [
415✔
205
                'callable' => function ($user_id) {
415✔
206
                    Helper::deprecated('{{ TimberUser() }}', '{{ get_user() }} or {{ get_users() }}', '2.0.0');
2✔
207
                    return Timber::get_user($user_id);
2✔
208
                },
415✔
209
                'options' => [
415✔
210
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
211
                        package: 'timber/timber',
415✔
212
                        version: '2.0.0',
415✔
213
                        altName: '{{ get_user() }} or {{ get_users() }}',
415✔
214
                        altPackage: null,
415✔
215
                        altVersion: null
415✔
216
                    ),
415✔
217
                ],
415✔
218
            ],
415✔
219
            'shortcode' => [
415✔
220
                'callable' => 'do_shortcode',
415✔
221
            ],
415✔
222
            'bloginfo' => [
415✔
223
                'callable' => 'bloginfo',
415✔
224
            ],
415✔
225

226
            // Translation functions.
227
            '__' => [
415✔
228
                'callable' => '__',
415✔
229
            ],
415✔
230
            'translate' => [
415✔
231
                'callable' => 'translate',
415✔
232
            ],
415✔
233
            '_e' => [
415✔
234
                'callable' => '_e',
415✔
235
            ],
415✔
236
            '_n' => [
415✔
237
                'callable' => '_n',
415✔
238
            ],
415✔
239
            '_x' => [
415✔
240
                'callable' => '_x',
415✔
241
            ],
415✔
242
            '_ex' => [
415✔
243
                'callable' => '_ex',
415✔
244
            ],
415✔
245
            '_nx' => [
415✔
246
                'callable' => '_nx',
415✔
247
            ],
415✔
248
            '_n_noop' => [
415✔
249
                'callable' => '_n_noop',
415✔
250
            ],
415✔
251
            '_nx_noop' => [
415✔
252
                'callable' => '_nx_noop',
415✔
253
            ],
415✔
254
            'translate_nooped_plural' => [
415✔
255
                'callable' => 'translate_nooped_plural',
415✔
256
            ],
415✔
257
        ];
415✔
258

259
        /**
260
         * Filters the functions that are added to Twig.
261
         *
262
         * The `$functions` array is an associative array with the filter name as a key and an
263
         * arguments array as the value. In the arguments array, you pass the function to call with
264
         * a `callable` entry.
265
         *
266
         * This is an alternative filter that you can use instead of adding your function in the
267
         * `timber/twig` filter.
268
         *
269
         * @api
270
         * @since 2.0.0
271
         * @example
272
         * ```php
273
         * add_filter( 'timber/twig/functions', function( $functions ) {
274
         *     // Add your own function.
275
         *     $functions['url_to_domain'] = [
276
         *         'callable' => 'url_to_domain',
277
         *     ];
278
         *
279
         *     // Replace a function.
280
         *     $functions['get_image'] = [
281
         *         'callable' => 'custom_image_get',
282
         *     ];
283
         *
284
         *     // Remove a function.
285
         *     unset( $functions['bloginfo'] );
286
         *
287
         *     return $functions;
288
         * } );
289
         * ```
290
         *
291
         * @param array $functions
292
         */
293
        $functions = \apply_filters('timber/twig/functions', $functions);
415✔
294

295
        return $functions;
415✔
296
    }
297

298
    /**
299
     * Adds Timber-specific functions to Twig.
300
     *
301
     * @param Environment $twig The Twig Environment.
302
     *
303
     * @return Environment
304
     */
305
    public function add_timber_functions($twig)
415✔
306
    {
307
        foreach ($this->get_timber_functions() as $name => $function) {
415✔
308
            $twig->addFunction(
415✔
309
                new TwigFunction(
415✔
310
                    $name,
415✔
311
                    $function['callable'],
415✔
312
                    $function['options'] ?? []
415✔
313
                )
415✔
314
            );
415✔
315
        }
316

317
        return $twig;
415✔
318
    }
319

320
    /**
321
     * Get Timber default filters
322
     *
323
     * @return array Default Timber filters
324
     */
325
    public function get_timber_filters()
415✔
326
    {
327
        $filters = [
415✔
328
            /* image filters */
329
            'resize' => [
415✔
330
                'callable' => [ImageHelper::class, 'resize'],
415✔
331
            ],
415✔
332
            'retina' => [
415✔
333
                'callable' => [ImageHelper::class, 'retina_resize'],
415✔
334
            ],
415✔
335
            'letterbox' => [
415✔
336
                'callable' => [ImageHelper::class, 'letterbox'],
415✔
337
            ],
415✔
338
            'tojpg' => [
415✔
339
                'callable' => [ImageHelper::class, 'img_to_jpg'],
415✔
340
            ],
415✔
341
            'towebp' => [
415✔
342
                'callable' => [ImageHelper::class, 'img_to_webp'],
415✔
343
            ],
415✔
344

345
            // Debugging filters.
346
            'get_class' => [
415✔
347
                'callable' => function ($obj) {
415✔
348
                    Helper::deprecated('{{ my_object | get_class }}', "{{ function('get_class', my_object) }}", '2.0.0');
1✔
349
                    return $obj::class;
1✔
350
                },
415✔
351
                'options' => [
415✔
352
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
353
                        package: 'timber/timber',
415✔
354
                        version: '2.0.0',
415✔
355
                        altName: "{{ function('get_class', my_object) }}",
415✔
356
                        altPackage: null,
415✔
357
                        altVersion: null
415✔
358
                    ),
415✔
359
                ],
415✔
360
            ],
415✔
361
            'print_r' => [
415✔
362
                'callable' => function ($arr) {
415✔
363
                    Helper::deprecated('{{ my_object | print_r }}', '{{ dump(my_object) }}', '2.0.0');
×
364
                    return \print_r($arr, true);
×
365
                },
415✔
366
                'options' => [
415✔
367
                    'deprecation_info' => new DeprecatedCallableInfo(
415✔
368
                        package: 'timber/timber',
415✔
369
                        version: '2.0.0',
415✔
370
                        altName: '{{ dump(my_object) }}',
415✔
371
                        altPackage: null,
415✔
372
                        altVersion: null
415✔
373
                    ),
415✔
374
                ],
415✔
375
            ],
415✔
376

377
            // Other filters.
378
            'stripshortcodes' => [
415✔
379
                'callable' => 'strip_shortcodes',
415✔
380
            ],
415✔
381
            'array' => [
415✔
382
                'callable' => [$this, 'to_array'],
415✔
383
            ],
415✔
384
            'excerpt' => [
415✔
385
                'callable' => 'wp_trim_words',
415✔
386
            ],
415✔
387
            'excerpt_chars' => [
415✔
388
                'callable' => [TextHelper::class, 'trim_characters'],
415✔
389
            ],
415✔
390
            'function' => [
415✔
391
                'callable' => [$this, 'exec_function'],
415✔
392
            ],
415✔
393
            'pretags' => [
415✔
394
                'callable' => [$this, 'twig_pretags'],
415✔
395
            ],
415✔
396
            'sanitize' => [
415✔
397
                'callable' => 'sanitize_title',
415✔
398
            ],
415✔
399
            'shortcodes' => [
415✔
400
                'callable' => 'do_shortcode',
415✔
401
            ],
415✔
402
            'wpautop' => [
415✔
403
                'callable' => 'wpautop',
415✔
404
            ],
415✔
405
            'list' => [
415✔
406
                'callable' => [$this, 'add_list_separators'],
415✔
407
            ],
415✔
408
            'pluck' => [
415✔
409
                'callable' => [Helper::class, 'pluck'],
415✔
410
            ],
415✔
411
            'wp_list_filter' => [
415✔
412
                'callable' => [Helper::class, 'wp_list_filter'],
415✔
413
            ],
415✔
414

415
            'relative' => [
415✔
416
                'callable' => fn ($link) => URLHelper::get_rel_url($link, true),
415✔
417
            ],
415✔
418

419
            /**
420
             * Date and Time filters.
421
             */
422
            'date' => [
415✔
423
                'callable' => [$this, 'twig_date_format_filter'],
415✔
424
                'options' => [
415✔
425
                    'needs_environment' => true,
415✔
426
                ],
415✔
427
            ],
415✔
428
            'time_ago' => [
415✔
429
                'callable' => [DateTimeHelper::class, 'time_ago'],
415✔
430
            ],
415✔
431
            'truncate' => [
415✔
432
                'callable' => fn ($text, $len) => TextHelper::trim_words($text, $len),
415✔
433
            ],
415✔
434

435
            // Numbers filters
436
            'size_format' => [
415✔
437
                'callable' => 'size_format',
415✔
438
            ],
415✔
439

440
            // Actions and filters.
441
            'apply_filters' => [
415✔
442
                'callable' => function () {
415✔
443
                    $args = \func_get_args();
1✔
444
                    $tag = \current(\array_splice($args, 1, 1));
1✔
445

446
                    return \apply_filters_ref_array($tag, $args);
1✔
447
                },
415✔
448
            ],
415✔
449
        ];
415✔
450

451
        /**
452
         * Filters the filters that are added to Twig.
453
         *
454
         * The `$filters` array is an associative array with the filter name as a key and an
455
         * arguments array as the value. In the arguments array, you pass the function to call with
456
         * a `callable` entry.
457
         *
458
         * This is an alternative filter that you can use instead of adding your filter in the
459
         * `timber/twig` filter.
460
         *
461
         * @api
462
         * @since 2.0.0
463
         * @example
464
         * ```php
465
         * add_filter( 'timber/twig/default_filters', function( $filters ) {
466
         *     // Add your own filter.
467
         *     $filters['price'] = [
468
         *         'callable' => 'format_price',
469
         *     ];
470
         *
471
         *     // Replace a filter.
472
         *     $filters['list'] = [
473
         *         'callable' => 'custom_list_filter',
474
         *     ];
475
         *
476
         *     // Remove a filter.
477
         *     unset( $filters['list'] );
478
         *
479
         *     return $filters;
480
         * } );
481
         * ```
482
         *
483
         * @param array $filters
484
         */
485
        $filters = \apply_filters('timber/twig/filters', $filters);
415✔
486

487
        return $filters;
415✔
488
    }
489

490
    /**
491
     * Get Timber default filters
492
     *
493
     * @return array Default Timber filters
494
     */
495
    public function get_timber_escaper_filters()
415✔
496
    {
497
        $escaper_filters = [
415✔
498
            'esc_url' => [
415✔
499
                'callable' => 'esc_url',
415✔
500
            ],
415✔
501
            'wp_kses' => [
415✔
502
                'callable' => 'wp_kses',
415✔
503
            ],
415✔
504
            'wp_kses_post' => [
415✔
505
                'callable' => 'wp_kses_post',
415✔
506
            ],
415✔
507
            'esc_attr' => [
415✔
508
                'callable' => 'esc_attr',
415✔
509
            ],
415✔
510
            'esc_html' => [
415✔
511
                'callable' => 'esc_html',
415✔
512
            ],
415✔
513
            'esc_js' => [
415✔
514
                'callable' => 'esc_js',
415✔
515
            ],
415✔
516
        ];
415✔
517

518
        /**
519
         * Filters the escaping filters that are added to Twig.
520
         *
521
         * The `$escaper_filters` array is an associative array with the filter name as a key and an
522
         * arguments array as the value. In the arguments array, you pass the function to call with
523
         * a `callable` entry.
524
         *
525
         *
526
         * @api
527
         * @since 2.1.0
528
         * @example
529
         * ```php
530
         * add_filter( 'timber/twig/escapers', function( $escaper_filters ) {
531
         *     // Add your own filter.
532
         *     $filters['esc_xml'] = [
533
         *         'callable' => 'esc_xml',
534
         *          'options' => [
535
         *             'is_safe' => ['html'],
536
         *          ],
537
         *     ];
538
         *
539
         *     // Remove a filter.
540
         *     unset( $filters['esc_js'] );
541
         *
542
         *     return $filters;
543
         * } );
544
         * ```
545
         *
546
         * @param array $escaper_filters
547
         */
548
        $escaper_filters = \apply_filters('timber/twig/escapers', $escaper_filters);
415✔
549

550
        return $escaper_filters;
415✔
551
    }
552

553
    /**
554
     * Adds filters to Twig.
555
     *
556
     * @param Environment $twig The Twig Environment.
557
     *
558
     * @return Environment
559
     */
560
    public function add_timber_filters($twig)
415✔
561
    {
562
        foreach ($this->get_timber_filters() as $name => $function) {
415✔
563
            $twig->addFilter(
415✔
564
                new TwigFilter(
415✔
565
                    $name,
415✔
566
                    $function['callable'],
415✔
567
                    $function['options'] ?? []
415✔
568
                )
415✔
569
            );
415✔
570
        }
571

572
        return $twig;
415✔
573
    }
574

575
    public function add_timber_escaper_filters($twig)
415✔
576
    {
577
        foreach ($this->get_timber_escaper_filters() as $name => $function) {
415✔
578
            $twig->addFilter(
415✔
579
                new TwigFilter(
415✔
580
                    $name,
415✔
581
                    $function['callable'],
415✔
582
                    $function['options'] ?? [
415✔
583
                        'is_safe' => ['html'],
415✔
584
                    ]
585
                )
415✔
586
            );
415✔
587
        }
588

589
        return $twig;
415✔
590
    }
591

592
    /**
593
     * Adds escapers.
594
     *
595
     * @param Environment $twig The Twig Environment.
596
     * @return Environment
597
     */
598
    public function add_timber_escapers($twig)
415✔
599
    {
600
        $esc_url = fn (Environment $env, $string) => \esc_url($string);
415✔
601

602
        $wp_kses_post = fn (Environment $env, $string) => \wp_kses_post($string);
415✔
603

604
        $esc_html = fn (Environment $env, $string) => \esc_html($string);
415✔
605

606
        $esc_js = fn (Environment $env, $string) => \esc_js($string);
415✔
607

608
        if (\class_exists(EscaperRuntime::class)) {
415✔
609
            $escaper_extension = $twig->getRuntime(EscaperRuntime::class);
415✔
610
            $escaper_extension->setEscaper('esc_url', '\esc_url');
415✔
611
            $escaper_extension->setEscaper('wp_kses_post', '\wp_kses_post');
415✔
612
            $escaper_extension->setEscaper('esc_html', '\esc_html');
415✔
613
            $escaper_extension->setEscaper('esc_js', '\esc_js');
415✔
614
        } elseif ($twig->hasExtension(EscaperExtension::class)) {
×
615
            $escaper_extension = $twig->getExtension(EscaperExtension::class);
×
616
            $escaper_extension->setEscaper('esc_url', $esc_url);
×
617
            $escaper_extension->setEscaper('wp_kses_post', $wp_kses_post);
×
618
            $escaper_extension->setEscaper('esc_html', $esc_html);
×
619
            $escaper_extension->setEscaper('esc_js', $esc_js);
×
620
        }
621

622
        return $twig;
415✔
623
    }
624

625
    /**
626
     * Overwrite Twig defaults.
627
     *
628
     * Makes Twig compatible with how WordPress handles dates, timezones, numbers and perhaps other items in
629
     * the future
630
     *
631
     * @since 2.0.0
632
     *
633
     * @throws RuntimeError
634
     * @param Environment $twig Twig Environment.
635
     *
636
     * @return Environment
637
     */
638
    public function set_defaults(Environment $twig)
415✔
639
    {
640
        $twig->getExtension(CoreExtension::class)->setDateFormat(\get_option('date_format'), '%d days');
415✔
641
        $twig->getExtension(CoreExtension::class)->setTimezone(\wp_timezone_string());
415✔
642

643
        /** @see https://developer.wordpress.org/reference/functions/number_format_i18n/ */
644
        global $wp_locale;
645
        if (isset($wp_locale)) {
415✔
646
            $twig->getExtension(CoreExtension::class)->setNumberFormat(0, $wp_locale->number_format['decimal_point'], $wp_locale->number_format['thousands_sep']);
415✔
647
        }
648

649
        return $twig;
415✔
650
    }
651

652
    /**
653
     * Converts a date to the given format.
654
     *
655
     * @internal
656
     * @since 2.0.0
657
     * @see  twig_date_format_filter()
658
     * @link https://twig.symfony.com/doc/2.x/filters/date.html
659
     *
660
     * @throws Exception
661
     *
662
     * @param Environment         $env      Twig Environment.
663
     * @param null|string|int|DateTime $date     A date.
664
     * @param null|string               $format   Optional. PHP date format. Will return the
665
     *                                            current date as a DateTimeImmutable object by
666
     *                                            default.
667
     * @param null                      $timezone Optional. The target timezone. Use `null` to use
668
     *                                            the default or
669
     *                                            `false` to leave the timezone unchanged.
670
     *
671
     * @return false|string A formatted date.
672
     */
673
    public function twig_date_format_filter(Environment $env, $date = null, $format = null, $timezone = null)
70✔
674
    {
675
        // Support for DateInterval.
676
        if ($date instanceof DateInterval) {
70✔
677
            if (null === $format) {
5✔
678
                $format = $env->getExtension(CoreExtension::class)->getDateFormat()[1];
2✔
679
            }
680

681
            return $date->format($format);
5✔
682
        }
683

684
        if (null === $date || 'now' === $date) {
65✔
685
            return DateTimeHelper::wp_date($format, null);
2✔
686
        }
687

688
        /**
689
         * If a string is given and it’s not a timestamp (e.g. "2010-01-28T15:00:00+04:00", try creating a DateTime
690
         * object and read the timezone from that string.
691
         */
692
        if (\is_string($date) && !\ctype_digit($date)) {
63✔
693
            $date_obj = \date_create($date);
17✔
694

695
            if ($date_obj) {
17✔
696
                $date = $date_obj;
17✔
697
            }
698
        }
699

700
        /**
701
         * Check for `false` parameter in |date filter in Twig
702
         *
703
         * @link https://twig.symfony.com/doc/2.x/filters/date.html#timezone
704
         */
705
        if (false === $timezone && $date instanceof DateTimeInterface) {
63✔
706
            $timezone = $date->getTimezone();
7✔
707
        }
708

709
        return DateTimeHelper::wp_date($format, $date, $timezone);
63✔
710
    }
711

712
    /**
713
     *
714
     *
715
     * @return array
716
     */
717
    public function to_array(mixed $arr)
2✔
718
    {
719
        if (\is_array($arr)) {
2✔
720
            return $arr;
1✔
721
        }
722
        $arr = [$arr];
1✔
723
        return $arr;
1✔
724
    }
725

726
    /**
727
     *
728
     *
729
     * @param string  $function_name
730
     * @return mixed
731
     */
732
    public function exec_function($function_name)
8✔
733
    {
734
        $args = \func_get_args();
8✔
735
        \array_shift($args);
8✔
736
        if (\is_string($function_name)) {
8✔
737
            $function_name = \trim($function_name);
8✔
738
        }
739
        return \call_user_func_array($function_name, ($args));
8✔
740
    }
741

742
    /**
743
     *
744
     *
745
     * @param string  $content
746
     * @return string
747
     */
748
    public function twig_pretags($content)
1✔
749
    {
750
        return \preg_replace_callback('|<pre.*>(.*)</pre|isU', [&$this, 'convert_pre_entities'], $content);
1✔
751
    }
752

753
    /**
754
     *
755
     *
756
     * @param array   $matches
757
     * @return string
758
     */
759
    public function convert_pre_entities($matches)
1✔
760
    {
761
        return \str_replace($matches[1], \htmlentities((string) $matches[1]), (string) $matches[0]);
1✔
762
    }
763

764
    /**
765
     * Formats a date.
766
     *
767
     * @deprecated 2.0.0
768
     *
769
     * @param null|string|false    $format Optional. PHP date format. Will use the `date_format`
770
     *                                     option as a default.
771
     * @param string|int|DateTime $date   A date.
772
     *
773
     * @return string
774
     */
775
    public function intl_date($date, $format = null)
×
776
    {
777
        Helper::deprecated('intl_date', 'DateTimeHelper::wp_date', '2.0.0');
×
778

779
        return DateTimeHelper::wp_date($format, $date);
×
780
    }
781

782
    /**
783
     *
784
     * @deprecated 2.0.0
785
     *
786
     * Returns the difference between two times in a human readable format.
787
     *
788
     * Differentiates between past and future dates.
789
     *
790
     * @see \human_time_diff()
791
     *
792
     * @param int|string $from          Base date as a timestamp or a date string.
793
     * @param int|string $to            Optional. Date to calculate difference to as a timestamp or
794
     *                                  a date string. Default to current time.
795
     * @param string     $format_past   Optional. String to use for past dates. To be used with
796
     *                                  `sprintf()`. Default `%s ago`.
797
     * @param string     $format_future Optional. String to use for future dates. To be used with
798
     *                                  `sprintf()`. Default `%s from now`.
799
     *
800
     * @return string
801
     */
802
    public static function time_ago($from, $to = null, $format_past = null, $format_future = null)
×
803
    {
804
        Helper::deprecated('time_ago', 'DateTimeHelper::time_ago', '2.0.0');
×
805

806
        return DateTimeHelper::time_ago($from, $to, $format_past, $format_future);
×
807
    }
808

809
    /**
810
     * @param array $arr
811
     * @param string $first_delimiter
812
     * @param string $second_delimiter
813
     * @return string
814
     */
815
    public function add_list_separators($arr, $first_delimiter = ',', $second_delimiter = ' and')
2✔
816
    {
817
        $length = \count($arr);
2✔
818
        $list = '';
2✔
819
        foreach ($arr as $index => $item) {
2✔
820
            if ($index < $length - 2) {
2✔
821
                $delimiter = $first_delimiter . ' ';
2✔
822
            } elseif ($index == $length - 2) {
2✔
823
                $delimiter = $second_delimiter . ' ';
2✔
824
            } else {
825
                $delimiter = '';
2✔
826
            }
827
            $list = $list . $item . $delimiter;
2✔
828
        }
829
        return $list;
2✔
830
    }
831
}
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