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

timber / timber / 5690057835

pending completion
5690057835

push

github

nlemoine
Merge branch '2.x' of github.com:timber/timber into 2.x-refactor-file-models

# Conflicts:
#	src/Attachment.php
#	src/ExternalImage.php
#	src/FileSize.php
#	src/URLHelper.php

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

3923 of 4430 relevant lines covered (88.56%)

59.08 hits per line

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

82.77
/src/Timber.php
1
<?php
2

3
namespace Timber;
4

5
use Timber\Factory\CommentFactory;
6
use Timber\Factory\MenuFactory;
7
use Timber\Factory\PagesMenuFactory;
8
use Timber\Factory\PostFactory;
9
use Timber\Factory\TermFactory;
10
use Timber\Factory\UserFactory;
11
use Timber\Integration\IntegrationInterface;
12

13
use WP_Comment;
14
use WP_Comment_Query;
15
use WP_Post;
16
use WP_Query;
17
use WP_Term;
18
use WP_User;
19

20
/**
21
 * Class Timber
22
 *
23
 * Main class called Timber for this plugin.
24
 *
25
 * @api
26
 * @example
27
 * ```php
28
 * // Get default posts on an archive page
29
 * $posts = Timber::get_posts();
30
 *
31
 * // Query for some posts
32
 * $posts = Timber::get_posts( [
33
 *     'post_type' => 'article',
34
 *     'category_name' => 'sports',
35
 * ] );
36
 *
37
 * $context = Timber::context( [
38
 *     'posts' => $posts,
39
 * ] );
40
 *
41
 * Timber::render( 'index.twig', $context );
42
 * ```
43
 */
44
class Timber
45
{
46
    public static $version = '2.0.0';
47

48
    public static $locations;
49

50
    public static $dirname = 'views';
51

52
    public static $auto_meta = true;
53

54
    /**
55
     * Global context cache.
56
     *
57
     * @var array An array containing global context variables.
58
     */
59
    public static $context_cache = [];
60

61
    /**
62
     * Caching option for Twig.
63
     *
64
     * @deprecated 2.0.0
65
     * @var bool
66
     */
67
    public static $twig_cache = false;
68

69
    /**
70
     * Caching option for Twig.
71
     *
72
     * Alias for `Timber::$twig_cache`.
73
     *
74
     * @deprecated 2.0.0
75
     * @var bool
76
     */
77
    public static $cache = false;
78

79
    /**
80
     * Autoescaping option for Twig.
81
     *
82
     * @deprecated 2.0.0
83
     * @var bool
84
     */
85
    public static $autoescape = false;
86

87
    /**
88
     * Timber should be loaded with Timber\Timber::init() and not new Timber\Timber();
89
     *
90
     * @codeCoverageIgnore
91
     */
92
    protected function __construct()
93
    {
94
    }
95

96
    protected function init_constants()
97
    {
98
        \defined("TIMBER_LOC") or \define("TIMBER_LOC", \realpath(\dirname(__DIR__)));
×
99
    }
100

101
    /**
102
     * @codeCoverageIgnore
103
     */
104
    public static function init()
105
    {
106
        if (!\defined('ABSPATH')
107
            || !\class_exists('\WP')
108
            || \defined('TIMBER_LOADED')
109
        ) {
110
            return;
111
        }
112

113
        $self = new self();
114
        $self->init_constants();
115

116
        Twig::init();
117
        ImageHelper::init();
118

119
        \add_action('init', [__CLASS__, 'init_integrations']);
120
        \add_action('admin_init', [Admin::class, 'init']);
121

122
        \add_filter('timber/post/import_data', [__CLASS__, 'handle_preview'], 10, 2);
123

124
        /**
125
         * Make an alias for the Timber class.
126
         *
127
         * This way, developers can use Timber::render() instead of Timber\Timber::render, which
128
         * is more user-friendly.
129
         */
130
        \class_alias('Timber\Timber', 'Timber');
131

132
        \define('TIMBER_LOADED', true);
133
    }
134

135
    /**
136
     * Initializes Timber's integrations.
137
     *
138
     * @return void
139
     */
140
    public static function init_integrations(): void
141
    {
142
        $integrations = [
×
143
            new Integration\AcfIntegration(),
×
144
            new Integration\CoAuthorsPlusIntegration(),
×
145
            new Integration\WpCliIntegration(),
×
146
            new Integration\WpmlIntegration(),
×
147
        ];
×
148

149
        /**
150
         * Filters the integrations that should be initialized by Timber.
151
         *
152
         * @since 2.0.0
153
         *
154
         * @param IntegrationInterface[] $integrations An array of PHP class names. Default: array of
155
         *                            integrations that Timber initializes by default.
156
         */
157
        $integrations = \apply_filters('timber/integrations', $integrations);
×
158

159
        // Integration classes must implement the IntegrationInterface.
160
        $integrations = \array_filter($integrations, static function ($integration) {
×
161
            return $integration instanceof IntegrationInterface;
×
162
        });
×
163

164
        foreach ($integrations as $integration) {
×
165
            if (!$integration->should_init()) {
×
166
                continue;
×
167
            }
168
            $integration->init();
×
169
        }
170
    }
171

172
    /**
173
     * Handles previewing posts.
174
     *
175
     * @param array $data
176
     * @param Post $post
177
     * @return array
178
     */
179
    public static function handle_preview($data, $post)
180
    {
181
        if (!isset($_GET['preview']) || !isset($_GET['preview_id'])) {
420✔
182
            return $data;
419✔
183
        }
184

185
        $preview_post_id = (int) $_GET['preview_id'];
2✔
186
        $current_post_id = $post->ID ?? null;
2✔
187
        // ⚠️ Don't filter imported data if the current post ID is not the preview post ID.
188
        // You might alter every `Timber::get_post()`!
189
        if ($current_post_id !== $preview_post_id) {
2✔
190
            return $data;
1✔
191
        }
192

193
        $preview = \wp_get_post_autosave($preview_post_id);
2✔
194

195
        if (\is_object($preview)) {
2✔
196
            $preview = \sanitize_post($preview);
2✔
197

198
            $data['post_content'] = $preview->post_content;
2✔
199
            $data['post_title'] = $preview->post_title;
2✔
200
            $data['post_excerpt'] = $preview->post_excerpt;
2✔
201

202
            \add_filter('get_the_terms', '_wp_preview_terms_filter', 10, 3);
2✔
203
        }
204

205
        return $data;
2✔
206
    }
207

208
    /* Post Retrieval Routine
209
    ================================ */
210

211
    /**
212
     * Gets a Timber Post from a post ID, WP_Post object, a WP_Query object, or an associative
213
     * array of arguments for WP_Query::__construct().
214
     *
215
     * By default, Timber will use the `Timber\Post` class to create a new post object. To control
216
     * which class is instantiated for your Post object, use [Class Maps](https://timber.github.io/docs/v2/guides/class-maps/)
217
     *
218
     * @api
219
     * @example
220
     * ```php
221
     * // Using a post ID.
222
     * $post = Timber::get_post( 75 );
223
     *
224
     * // Using a WP_Post object.
225
     * $wp_post = get_post( 123 );
226
     * $post    = Timber::get_post( $wp_post );
227
     *
228
     * // Using a WP_Query argument array
229
     * $post = Timber::get_post( [
230
     *   'post_type' => 'page',
231
     * ] );
232
     *
233
     * // Use currently queried post. Same as using get_the_ID() as a parameter.
234
     * $post = Timber::get_post();
235
     *
236
     * // From an associative array with an `ID` key. For ACF compatibility. Using this
237
     * // approach directly is not recommended. If you can, configure the return type of your
238
     * // ACF field to just the ID.
239
     * $post = Timber::get_post( get_field('associated_post_array') ); // Just OK.
240
     * $post = Timber::get_post( get_field('associated_post_id') ); // Better!
241
     * ```
242
     * @see https://developer.wordpress.org/reference/classes/wp_query/__construct/
243
     *
244
     * @param mixed $query   Optional. Post ID or query (as an array of arguments for WP_Query).
245
     *                              If a query is provided, only the first post of the result will be
246
     *                       returned. Default false.
247
     * @param array $options Optional associative array of options. Defaults to an empty array.
248
     *
249
     * @return \Timber\Post|null Timber\Post object if a post was found, null if no post was
250
     *                           found.
251
     */
252
    public static function get_post($query = false, $options = [])
253
    {
254
        if (\is_string($query) && !\is_numeric($query)) {
364✔
255
            Helper::doing_it_wrong(
1✔
256
                'Timber::get_post()',
1✔
257
                'Getting a post by post slug or post name was removed from Timber::get_post() in Timber 2.0. Use Timber::get_post_by() instead.',
1✔
258
                '2.0.0'
1✔
259
            );
1✔
260
        }
261

262
        if (\is_string($options)) {
364✔
263
            Helper::doing_it_wrong(
1✔
264
                'Timber::get_post()',
1✔
265
                'The $PostClass parameter for passing in the post class to use in Timber::get_posts() was replaced with an $options array in Timber 2.0. To customize which class to instantiate for your post, use Class Maps instead: https://timber.github.io/docs/v2/guides/class-maps/',
1✔
266
                '2.0.0'
1✔
267
            );
1✔
268

269
            $options = [];
1✔
270
        }
271

272
        $factory = new PostFactory();
364✔
273

274
        global $wp_query;
275

276
        $options = \wp_parse_args($options, [
364✔
277
            'merge_default' => false,
364✔
278
        ]);
364✔
279

280
        // Has WP already queried and found a post?
281
        if ($query === false && ($wp_query->queried_object instanceof WP_Post)) {
364✔
282
            $query = $wp_query->queried_object;
15✔
283
        } elseif (\is_array($query) && $options['merge_default']) {
355✔
284
            $query = \wp_parse_args($wp_query->query_vars);
1✔
285
        }
286

287
        // Default to the global query.
288
        $result = $factory->from($query ?: $wp_query);
364✔
289

290
        // If we got a Collection, return the first Post.
291
        if ($result instanceof PostCollectionInterface) {
364✔
292
            return $result[0] ?? null;
18✔
293
        }
294

295
        return $result;
348✔
296
    }
297

298
    /**
299
     * Gets an attachment.
300
     *
301
     * Behaves just like Timber::get_post(), except that it returns null if it finds a Timber\Post
302
     * that is not an Attachment. Honors Class Maps and falsifies return value *after* Class Map for
303
     * the found Timber\Post has been resolved.
304
     *
305
     * @api
306
     * @since 2.0.0
307
     * @see Timber::get_post()
308
     * @see https://timber.github.io/docs/v2/guides/class-maps/
309
     *
310
     * @param mixed $query   Optional. Query or post identifier. Default false.
311
     * @param array $options Optional. Options for Timber\Timber::get_post().
312
     *
313
     * @return Attachment|null Timber\Attachment object if an attachment was found, null if no
314
     *                         attachment was found.
315
     */
316
    public static function get_attachment($query = false, $options = [])
317
    {
318
        $post = static::get_post($query, $options);
2✔
319

320
        // No need to instantiate a Post we're not going to use.
321
        return ($post instanceof Attachment) ? $post : null;
2✔
322
    }
323

324
    /**
325
     * Gets an image.
326
     *
327
     * Behaves just like Timber::get_post(), except that it returns null if it finds a Timber\Post
328
     * that is not an Image. Honors Class Maps and falsifies return value *after* Class Map for the
329
     * found Timber\Post has been resolved.
330
     *
331
     * @api
332
     * @since 2.0.0
333
     * @see Timber::get_post()
334
     * @see https://timber.github.io/docs/v2/guides/class-maps/
335
     *
336
     * @param mixed $query   Optional. Query or post identifier. Default false.
337
     * @param array $options Optional. Options for Timber\Timber::get_post().
338
     *
339
     * @return Image|null
340
     */
341
    public static function get_image($query = false, $options = [])
342
    {
343
        $post = static::get_post($query, $options);
7✔
344

345
        // No need to instantiate a Post we're not going to use.
346
        return ($post instanceof Image) ? $post : null;
7✔
347
    }
348

349
    /**
350
     * Gets an external image.
351
     *
352
     * Behaves just like Timber::get_image(), except that you can use an absolute or relative path or a URL to load an
353
     * image. You can also pass in an external URL. In that case, Timber will sideload the image and store it in the
354
     * uploads folder of your WordPress installation. The next time the image is accessed, it will be loaded from there.
355
     *
356
     * @api
357
     * @since 2.0.0
358
     * @see Timber::get_image()
359
     * @see ImageHelper::sideload_image()
360
     *
361
     * @param bool  $url Image path or URL. The path can be absolute or relative to the WordPress installation.
362
     * @param array $args {
363
     *     An associative array with additional arguments for the image.
364
     *
365
     *     @type string $alt Alt text for the image.
366
     *     @type string $caption Caption text for the image.
367
     * }
368
     *
369
     * @return ExternalImage|null
370
     */
371
    public static function get_external_image($url = false, array $args = []): ?ExternalImage
372
    {
373
        $args = \wp_parse_args($args, [
16✔
374
            'alt' => '',
16✔
375
            'caption' => '',
16✔
376
        ]);
16✔
377

378
        return ExternalImage::build($url, $args);
16✔
379
    }
380

381
    /**
382
     * Gets a collection of posts.
383
     *
384
     * Refer to the official documentation for
385
     * [WP_Query](https://developer.wordpress.org/reference/classes/wp_query/) for a list of all
386
     * the arguments that can be used for the `$query` parameter.
387
     *
388
     * @api
389
     * @example
390
     * ```php
391
     * // Use the global query.
392
     * $posts = Timber::get_posts();
393
     *
394
     * // Using the WP_Query argument format.
395
     * $posts = Timber::get_posts( [
396
     *    'post_type'     => 'article',
397
     *    'category_name' => 'sports',
398
     * ] );
399
     *
400
     * // Using a WP_Query instance.
401
     * $posts = Timber::get_posts( new WP_Query( [ 'post_type' => 'any' ) );
402
     *
403
     * // Using an array of post IDs.
404
     * $posts = Timber::get_posts( [ 47, 543, 3220 ] );
405
     * ```
406
     *
407
     * @param mixed $query  Optional. Query args. Default `false`, which means that Timber will use
408
     *                      the global query. Accepts an array of `WP_Query` arguments, a `WP_Query`
409
     *                      instance or a list of post IDs.
410
     * @param array $options {
411
     *     Optional. Options for the query.
412
     *
413
     *     @type bool $merge_default    Merge query parameters with the default query parameters of
414
     *                                  the current template. Default false.
415
     * }
416
     *
417
     * @return \Timber\PostCollectionInterface|null Null if no query could be run with the used
418
     *                                              query parameters.
419
     */
420
    public static function get_posts($query = false, $options = [])
421
    {
422
        if (\is_string($query)) {
68✔
423
            Helper::doing_it_wrong(
2✔
424
                'Timber::get_posts()',
2✔
425
                "Querying posts by using a query string was removed in Timber 2.0. Pass in the query string as an options array instead. For example, change Timber::get_posts( 'post_type=portfolio&posts_per_page=3') to Timber::get_posts( [ 'post_type' => 'portfolio', 'posts_per_page' => 3 ] ). Learn more: https://timber.github.io/docs/v2/reference/timber-timber/#get_posts",
2✔
426
                '2.0.0'
2✔
427
            );
2✔
428

429
            $query = new WP_Query($query);
2✔
430
        }
431

432
        if (\is_string($options)) {
68✔
433
            Helper::doing_it_wrong(
2✔
434
                'Timber::get_posts()',
2✔
435
                'The $PostClass parameter for passing in the post class to use in Timber::get_posts() was replaced with an $options array in Timber 2.0. To customize which class to instantiate for your post, use Class Maps instead: https://timber.github.io/docs/v2/guides/class-maps/',
2✔
436
                '2.0.0'
2✔
437
            );
2✔
438
            $options = [];
2✔
439
        }
440

441
        if (3 === \func_num_args()) {
68✔
442
            Helper::doing_it_wrong(
1✔
443
                'Timber::get_posts()',
1✔
444
                'The $return_collection parameter to control whether a post collection is returned in Timber::get_posts() was removed in Timber 2.0.',
1✔
445
                '2.0.0'
1✔
446
            );
1✔
447
        }
448

449
        if (\is_array($query) && isset($query['numberposts'])) {
68✔
450
            Helper::doing_it_wrong(
1✔
451
                'Timber::get_posts()',
1✔
452
                'Using `numberposts` only works when using `get_posts()`, but not for Timber::get_posts(). Use `posts_per_page` instead.',
1✔
453
                '2.0.0'
1✔
454
            );
1✔
455
        }
456

457
        /**
458
         * @todo Are there any more default options to support?
459
         */
460
        $options = \wp_parse_args($options, [
68✔
461
            'merge_default' => false,
68✔
462
        ]);
68✔
463

464
        global $wp_query;
465

466
        if (\is_array($query) && $options['merge_default']) {
68✔
467
            $query = \wp_parse_args($query, $wp_query->query_vars);
1✔
468
        }
469

470
        $factory = new PostFactory();
68✔
471

472
        // Default to the global query.
473
        return $factory->from($query ?: $wp_query);
68✔
474
    }
475

476
    /**
477
     * Gets a post by title or slug.
478
     *
479
     * @api
480
     * @since 2.0.0
481
     * @example
482
     * ```php
483
     * // By slug
484
     * $post = Timber::get_post_by( 'slug', 'about-us' );
485
     *
486
     * // By title
487
     * $post = Timber::get_post_by( 'title', 'About us' );
488
     * ```
489
     *
490
     * @param string       $type         The type to look for. One of `slug` or `title`.
491
     * @param string       $search_value The post slug or post title to search for. When searching
492
     *                                   for `title`, this parameter doesn’t need to be
493
     *                                   case-sensitive, because the `=` comparison is used in
494
     *                                   MySQL.
495
     * @param array        $args {
496
     *     Optional. An array of arguments to configure what is returned.
497
     *
498
     *            @type string|array     $post_type   Optional. What WordPress post type to limit the
499
     *                                         results to. Defaults to 'any'
500
     *     @type string           $order_by    Optional. The field to sort by. Defaults to
501
     *                                         'post_date'
502
     *     @type string           $order       Optional. The sort to apply. Defaults to ASC
503
     *
504
     * }
505
     *
506
     * @return \Timber\Post|null A Timber post or `null` if no post could be found. If multiple
507
     *                           posts with the same slug or title were found, it will select the
508
     *                           post with the oldest date.
509
     */
510
    public static function get_post_by($type, $search_value, $args = [])
511
    {
512
        $post_id = false;
9✔
513
        $args = \wp_parse_args($args, [
9✔
514
            'post_type' => 'any',
9✔
515
            'order_by' => 'post_date',
9✔
516
            'order' => 'ASC',
9✔
517
        ]);
9✔
518
        if ('slug' === $type) {
9✔
519
            $args = \wp_parse_args($args, [
5✔
520
                'name' => $search_value,
5✔
521
                'fields' => 'ids',
5✔
522
            ]);
5✔
523
            $query = new WP_Query($args);
5✔
524

525
            if ($query->post_count < 1) {
5✔
526
                return null;
1✔
527
            }
528

529
            $posts = $query->get_posts();
4✔
530
            $post_id = \array_shift($posts);
4✔
531
        } elseif ('title' === $type) {
4✔
532
            /**
533
             * The following section is inspired by post_exists() as well as get_page_by_title().
534
             *
535
             * These two functions always return the post with lowest ID. However, we want the post
536
             * with oldest post date.
537
             *
538
             * @see \post_exists()
539
             * @see \get_page_by_title()
540
             */
541
            global $wpdb;
542

543
            $sql = "SELECT ID FROM $wpdb->posts WHERE post_title = %s";
4✔
544
            $query_args = [$search_value];
4✔
545
            if (\is_array($args['post_type'])) {
4✔
546
                $post_type = \esc_sql($args['post_type']);
1✔
547
                $post_type_in_string = "'" . \implode("','", $args['post_type']) . "'";
1✔
548

549
                $sql .= " AND post_type IN ($post_type_in_string)";
1✔
550
            } elseif ('any' !== $args['post_type']) {
4✔
551
                $sql .= ' AND post_type = %s';
1✔
552
                $query_args[] = $args['post_type'];
1✔
553
            }
554

555
            // Always return the oldest post first.
556
            $sql .= ' ORDER BY post_date ASC';
4✔
557

558
            $post_id = $wpdb->get_var($wpdb->prepare($sql, $query_args));
4✔
559
        }
560

561
        if (!$post_id) {
8✔
562
            return null;
1✔
563
        }
564

565
        return self::get_post($post_id);
7✔
566
    }
567

568
    /**
569
     * Query post.
570
     *
571
     * @api
572
     * @deprecated since 2.0.0 Use `Timber::get_post()` instead.
573
     *
574
     * @param mixed $query
575
     * @param array $options
576
     *
577
     * @return Post|array|bool|null
578
     */
579
    public static function query_post($query = false, array $options = [])
580
    {
581
        Helper::deprecated('Timber::query_post()', 'Timber::get_post()', '2.0.0');
2✔
582

583
        return self::get_post($query, $options);
2✔
584
    }
585

586
    /**
587
     * Query posts.
588
     *
589
     * @api
590
     * @deprecated since 2.0.0 Use `Timber::get_posts()` instead.
591
     *
592
     * @param mixed $query
593
     * @param array $options
594
     *
595
     * @return \Timber\PostCollectionInterface
596
     */
597
    public static function query_posts($query = false, array $options = [])
598
    {
599
        Helper::deprecated('Timber::query_posts()', 'Timber::get_posts()', '2.0.0');
1✔
600

601
        return self::get_posts($query, $options);
1✔
602
    }
603

604
    /**
605
     * Gets an attachment by its URL or absolute file path.
606
     *
607
     * Honors the `timber/post/image_extensions` filter, returning a Timber\Image if the found
608
     * attachment is identified as an image. Also honors Class Maps.
609
     *
610
     * @api
611
     * @since 2.0.0
612
     * @example
613
     * ```php
614
     * // Get attachment by URL.
615
     * $attachment = Timber::get_attachment_by( 'url', 'https://example.com/uploads/2020/09/cat.gif' );
616
     *
617
     * // Get attachment by filepath.
618
     * $attachment = Timber::get_attachment_by( 'path', '/path/to/wp-content/uploads/2020/09/cat.gif' );
619
     *
620
     * // Try to handle either case.
621
     * $mystery_string = some_function();
622
     * $attachment     = Timber::get_attachment_by( $mystery_string );
623
     * ```
624
     *
625
     * @param string $field_or_ident Can be "url", "path", an attachment URL, or the absolute
626
     *                               path of an attachment file. If "url" or "path" is given, a
627
     *                               second arg is required.
628
     * @param string $ident          Optional. An attachment URL or absolute path. Default empty
629
     *                               string.
630
     *
631
     * @return \Timber\Attachment|null
632
     */
633
    public static function get_attachment_by(string $field_or_ident, string $ident = '')
634
    {
635
        if ($field_or_ident === 'url') {
10✔
636
            if (empty($ident)) {
9✔
637
                Helper::doing_it_wrong(
1✔
638
                    'Timber::get_attachment_by()',
1✔
639
                    'Passing "url" as the first arg requires passing a URL as the second arg.',
1✔
640
                    '2.0.0'
1✔
641
                );
1✔
642

643
                return null;
1✔
644
            }
645

646
            $id = \attachment_url_to_postid($ident);
8✔
647

648
            return $id ? (new PostFactory())->from($id) : null;
8✔
649
        }
650

651
        if ($field_or_ident === 'path') {
7✔
652
            if (empty($ident)) {
6✔
653
                Helper::doing_it_wrong(
1✔
654
                    'Timber::get_attachment_by()',
1✔
655
                    'Passing "path" as the first arg requires passing an absolute path as the second arg.',
1✔
656
                    '2.0.0'
1✔
657
                );
1✔
658

659
                return null;
1✔
660
            }
661

662
            if (!\file_exists($ident)) {
5✔
663
                // Deal with a relative path.
664
                $ident = URLHelper::get_full_path($ident);
2✔
665
            }
666

667
            return self::get_attachment_by('url', URLHelper::file_system_to_url($ident));
5✔
668
        }
669

670
        if (empty($ident)) {
3✔
671
            $field = URLHelper::starts_with($field_or_ident, ABSPATH) ? 'path' : 'url';
3✔
672

673
            return self::get_attachment_by($field, $field_or_ident);
3✔
674
        }
675

676
        return null;
×
677
    }
678

679
    /* Term Retrieval
680
    ================================ */
681

682
    /**
683
     * Gets terms.
684
     *
685
     * @api
686
     * @see https://developer.wordpress.org/reference/classes/wp_term_query/__construct/
687
     * @example
688
     * ```php
689
     * // Get all tags.
690
     * $tags = Timber::get_terms( 'post_tag' );
691
     * // Note that this is equivalent to:
692
     * $tags = Timber::get_terms( 'tag' );
693
     * $tags = Timber::get_terms( 'tags' );
694
     *
695
     * // Get all categories.
696
     * $cats = Timber::get_terms( 'category' );
697
     *
698
     * // Get all terms in a custom taxonomy.
699
     * $cats = Timber::get_terms('my_taxonomy');
700
     *
701
     * // Perform a custom Term query.
702
     * $cats = Timber::get_terms( [
703
     *   'taxonomy' => 'my_taxonomy',
704
     *   'orderby'  => 'slug',
705
     *   'order'    => 'DESC',
706
     * ] );
707
     * ```
708
     *
709
     * @param string|array $args    A string or array identifying the taxonomy or
710
     *                              `WP_Term_Query` args. Numeric strings are treated as term IDs;
711
     *                              non-numeric strings are treated as taxonomy names. Numeric
712
     *                              arrays are treated as a list a of term identifiers; associative
713
     *                              arrays are treated as args for `WP_Term_Query::__construct()`
714
     *                              and accept any valid parameters to that constructor.
715
     *                              Default `null`, which will get terms from all queryable
716
     *                              taxonomies.
717
     * @param array        $options Optional. None are currently supported. Default empty array.
718
     *
719
     * @return iterable
720
     */
721
    public static function get_terms($args = null, array $options = []): iterable
722
    {
723
        // default to all queryable taxonomies
724
        $args = $args ?? [
22✔
725
            'taxonomy' => \get_taxonomies(),
22✔
726
        ];
727

728
        $factory = new TermFactory();
22✔
729

730
        return $factory->from($args);
22✔
731
    }
732

733
    /**
734
     * Gets a term.
735
     *
736
     * @api
737
     * @param int|WP_Term $term A WP_Term or term_id
738
     * @return \Timber\Term|null
739
     * @example
740
     * ```php
741
     * // Get a Term.
742
     * $tag = Timber::get_term( 123 );
743
     * ```
744
     */
745
    public static function get_term($term = null)
746
    {
747
        if (null === $term) {
66✔
748
            // get the fallback term_id from the current query
749
            global $wp_query;
750
            $term = $wp_query->queried_object->term_id ?? null;
4✔
751
        }
752

753
        if (null === $term) {
66✔
754
            // not able to get term_id from the current query; bail
755
            return null;
×
756
        }
757

758
        $factory = new TermFactory();
66✔
759
        $terms = $factory->from($term);
66✔
760

761
        if (\is_array($terms)) {
66✔
762
            $terms = $terms[0];
3✔
763
        }
764

765
        return $terms;
66✔
766
    }
767

768
    /**
769
     * Gets a term by field.
770
     *
771
     * This function works like
772
     * [`get_term_by()`](https://developer.wordpress.org/reference/functions/get_term_by/), but
773
     * returns a `Timber\Term` object.
774
     *
775
     * @api
776
     * @since 2.0.0
777
     * @example
778
     * ```php
779
     * // Get a term by slug.
780
     * $term = Timber::get_term_by( 'slug', 'security' );
781
     *
782
     * // Get a term by name.
783
     * $term = Timber::get_term_by( 'name', 'Security' );
784
     *
785
     * // Get a term by slug from a specific taxonomy.
786
     * $term = Timber::get_term_by( 'slug', 'security', 'category' );
787
     * ```
788
     *
789
     * @param string     $field    The name of the field to retrieve the term with. One of: `id`,
790
     *                             `ID`, `slug`, `name` or `term_taxonomy_id`.
791
     * @param int|string $value    The value to search for by `$field`.
792
     * @param string     $taxonomy The taxonomy you want to retrieve from. Empty string will search
793
     *                             from all.
794
     *
795
     * @return \Timber\Term|null
796
     */
797
    public static function get_term_by(string $field, $value, string $taxonomy = '')
798
    {
799
        $wp_term = \get_term_by($field, $value, $taxonomy);
3✔
800

801
        if ($wp_term === false) {
3✔
802
            if (empty($taxonomy) && $field != 'term_taxonomy_id') {
2✔
803
                $search = [
2✔
804
                    $field => $value,
2✔
805
                    $taxonomy => 'any',
2✔
806
                    'hide_empty' => false,
2✔
807
                ];
2✔
808
                return static::get_term($search);
2✔
809
            }
810

811
            return null;
×
812
        }
813

814
        return static::get_term($wp_term);
2✔
815
    }
816

817
    /* User Retrieval
818
    ================================ */
819

820
    /**
821
     * Gets one or more users as an array.
822
     *
823
     * By default, Timber will use the `Timber\User` class to create a your post objects. To
824
     * control which class is used for your post objects, use [Class Maps]().
825
     *
826
     * @api
827
     * @since 2.0.0
828
     * @example
829
     * ```php
830
     * // Get users with on an array of user IDs.
831
     * $users = Timber::get_users( [ 24, 81, 325 ] );
832
     *
833
     * // Get all users that only have a subscriber role.
834
     * $subscribers = Timber::get_users( [
835
     *     'role' => 'subscriber',
836
     * ] );
837
     *
838
     * // Get all users that have published posts.
839
     * $post_authors = Timber::get_users( [
840
     *     'has_published_posts' => [ 'post' ],
841
     * ] );
842
     * ```
843
     *
844
     * @todo  Add links to Class Maps documentation in function summary.
845
     *
846
     * @param array $query   Optional. A WordPress-style query or an array of user IDs. Use an
847
     *                       array in the same way you would use the `$args` parameter in
848
     *                       [WP_User_Query](https://developer.wordpress.org/reference/classes/wp_user_query/).
849
     *                       See
850
     *                       [WP_User_Query::prepare_query()](https://developer.wordpress.org/reference/classes/WP_User_Query/prepare_query/)
851
     *                       for a list of all available parameters. Passing an empty parameter
852
     *                       will return an empty array. Default empty array
853
     *                       `[]`.
854
     * @param array $options Optional. An array of options. None are currently supported. This
855
     *                       parameter exists to prevent future breaking changes. Default empty
856
     *                       array `[]`.
857
     *
858
     * @return iterable An array of users objects. Will be empty if no users were found.
859
     */
860
    public static function get_users(array $query = [], array $options = []): iterable
861
    {
862
        $factory = new UserFactory();
2✔
863

864
        return $factory->from($query);
2✔
865
    }
866

867
    /**
868
     * Gets a single user.
869
     *
870
     * By default, Timber will use the `Timber\User` class to create a your post objects. To
871
     * control which class is used for your post objects, use [Class Maps]().
872
     *
873
     * @api
874
     * @since 2.0.0
875
     * @example
876
     * ```php
877
     * $current_user = Timber::get_user();
878
     *
879
     * // Get user by ID.
880
     * $user = Timber::get_user( $user_id );
881
     *
882
     * // Convert a WP_User object to a Timber\User object.
883
     * $user = Timber::get_user( $wp_user_object );
884
     *
885
     * // Check if a user is logged in.
886
     *
887
     * $user = Timber::get_user();
888
     *
889
     * if ( $user ) {
890
     *     // Yay, user is logged in.
891
     * }
892
     * ```
893
     *
894
     * @todo Add links to Class Maps documentation in function summary.
895
     *
896
     * @param int|WP_User $user A WP_User object or a WordPress user ID. Defaults to the ID of the
897
     *                           currently logged-in user.
898
     *
899
     * @return \Timber\User|null
900
     */
901
    public static function get_user($user = null)
902
    {
903
        /*
904
         * TODO in the interest of time, I'm implementing this logic here. If there's
905
         * a better place to do this or something that already implements this, let me know
906
         * and I'll switch over to that.
907
         */
908
        $user = $user ?: \get_current_user_id();
56✔
909

910
        $factory = new UserFactory();
56✔
911
        return $factory->from($user);
56✔
912
    }
913

914
    /**
915
     * Gets a user by field.
916
     *
917
     * This function works like
918
     * [`get_user_by()`](https://developer.wordpress.org/reference/functions/get_user_by/), but
919
     * returns a `Timber\User` object.
920
     *
921
     * @api
922
     * @since 2.0.0
923
     * @example
924
     * ```php
925
     * // Get a user by email.
926
     * $user = Timber::get_user_by( 'email', 'user@example.com' );
927
     *
928
     * // Get a user by login.
929
     * $user = Timber::get_user_by( 'login', 'keanu-reeves' );
930
     * ```
931
     *
932
     * @param string     $field The name of the field to retrieve the user with. One of: `id`,
933
     *                          `ID`, `slug`, `email` or `login`.
934
     * @param int|string $value The value to search for by `$field`.
935
     *
936
     * @return \Timber\User|null
937
     */
938
    public static function get_user_by(string $field, $value)
939
    {
940
        $wp_user = \get_user_by($field, $value);
4✔
941

942
        if ($wp_user === false) {
4✔
943
            return null;
1✔
944
        }
945

946
        return static::get_user($wp_user);
3✔
947
    }
948

949

950
    /* Menu Retrieval
951
    ================================ */
952

953
    /**
954
     * Gets a nav menu object.
955
     *
956
     * @api
957
     * @since 2.0.0
958
     * @example
959
     * ```php
960
     * // Get a menu by location
961
     * $menu = Timber::get_menu( 'primary-menu' );
962
     *
963
     * // Get a menu by slug
964
     * $menu = Timber::get_menu( 'my-menu' );
965
     *
966
     * // Get a menu by name
967
     * $menu = Timber::get_menu( 'Main Menu' );
968
     *
969
     * // Get a menu by ID (term_id)
970
     * $menu = Timber::get_menu( 123 );
971
     * ```
972
     *
973
     * @param int|string $identifier A menu identifier: a term_id, slug, menu name, or menu location name
974
     * @param array      $args An associative array of options. Currently only one option is
975
     * supported:
976
     * - `depth`: How deep down the tree of menu items to query. Useful if you only want
977
     *   the first N levels of items in the menu.
978
     *
979
     * @return \Timber\Menu|null
980
     */
981
    public static function get_menu($identifier = null, array $args = []): ?Menu
982
    {
983
        $factory = new MenuFactory();
55✔
984
        return $factory->from($identifier, $args);
55✔
985
    }
986

987
    /**
988
     * Gets a menu by field.
989
     *
990
     * @api
991
     * @since 2.0.0
992
     * @example
993
     * ```php
994
     * // Get a menu by location.
995
     * $menu = Timber::get_menu_by( 'location', 'primary' );
996
     *
997
     * // Get a menu by slug.
998
     * $menu = Timber::get_menu_by( 'slug', 'primary-menu' );
999
     * ```
1000
     *
1001
     * @param string     $field The name of the field to retrieve the menu with. One of: `id`,
1002
     *                          `ID`, `term_id`, `slug`, `name` or `location`.
1003
     * @param int|string $value The value to search for by `$field`.
1004
     *
1005
     * @return \Timber\Menu|null
1006
     */
1007
    public static function get_menu_by(string $field, $value, array $args = []): ?Menu
1008
    {
1009
        $factory = new MenuFactory();
×
1010
        $menu = null;
×
1011

1012
        switch ($field) {
1013
            case 'id':
×
1014
            case 'term_id':
×
1015
            case 'ID':
×
1016
                $menu = $factory->from_id($value, $args);
×
1017
                break;
×
1018
            case 'slug':
×
1019
                $menu = $factory->from_slug($value, $args);
×
1020
                break;
×
1021
            case 'name':
×
1022
                $menu = $factory->from_name($value, $args);
×
1023
                break;
×
1024
            case 'location':
×
1025
                $menu = $factory->from_location($value, $args);
×
1026
                break;
×
1027
        }
1028

1029
        return $menu;
×
1030
    }
1031

1032
    /**
1033
     * Gets a menu from the existing pages.
1034
     *
1035
     * @api
1036
     * @since 2.0.0
1037
     *
1038
     * @example
1039
     * ```php
1040
     * $menu = Timber::get_pages_menu();
1041
     * ```
1042
     *
1043
     * @param array $args Optional. Arguments for `wp_list_pages()`. Timber doesn’t use that
1044
     *                    function under the hood, but supports all arguments for that function.
1045
     *                    It will use `get_pages()` to get the pages that will be used for the Pages
1046
     *                    Menu.
1047
     */
1048
    public static function get_pages_menu(array $args = [])
1049
    {
1050
        $factory = new PagesMenuFactory();
9✔
1051

1052
        $menu = $factory->from_pages($args);
9✔
1053

1054
        return $menu;
9✔
1055
    }
1056

1057

1058
    /* Comment Retrieval
1059
    ================================ */
1060

1061
    /**
1062
     * Get comments.
1063
     *
1064
     * @api
1065
     * @since 2.0.0
1066
     *
1067
     * @param array|WP_Comment_Query $query
1068
     * @param array                   $options Optional. None are currently supported.
1069
     * @return array
1070
     */
1071
    public static function get_comments($query = [], array $options = []): iterable
1072
    {
1073
        $factory = new CommentFactory();
×
1074

1075
        return $factory->from($query);
×
1076
    }
1077

1078
    /**
1079
     * Gets comment.
1080
     *
1081
     * @api
1082
     * @since 2.0.0
1083
     * @param int|WP_Comment $comment
1084
     * @return \Timber\Comment|null
1085
     */
1086
    public static function get_comment($comment)
1087
    {
1088
        $factory = new CommentFactory();
43✔
1089
        return $factory->from($comment);
43✔
1090
    }
1091

1092
    /* Site Retrieval
1093
    ================================ */
1094

1095
    /**
1096
     * Get sites.
1097
     * @api
1098
     * @param array|bool $blog_ids
1099
     * @return array
1100
     */
1101
    public static function get_sites($blog_ids = false)
1102
    {
1103
        if (!\is_array($blog_ids)) {
×
1104
            global $wpdb;
1105
            $blog_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs ORDER BY blog_id ASC");
×
1106
        }
1107
        $return = [];
×
1108
        foreach ($blog_ids as $blog_id) {
×
1109
            $return[] = new Site($blog_id);
×
1110
        }
1111
        return $return;
×
1112
    }
1113

1114

1115
    /*  Template Setup and Display
1116
    ================================ */
1117

1118
    /**
1119
     * Get context.
1120
     * @api
1121
     * @deprecated 2.0.0, use `Timber::context()` instead.
1122
     *
1123
     * @return array
1124
     */
1125
    public static function get_context()
1126
    {
1127
        Helper::deprecated('get_context', 'context', '2.0.0');
×
1128

1129
        return self::context();
×
1130
    }
1131

1132
    /**
1133
     * Gets the global context.
1134
     *
1135
     * The context always contains the global context with the following variables:
1136
     *
1137
     * - `site` – An instance of `Timber\Site`.
1138
     * - `request` - An instance of `Timber\Request`.
1139
     * - `theme` - An instance of `Timber\Theme`.
1140
     * - `user` - An instance of `Timber\User`.
1141
     * - `http_host` - The HTTP host.
1142
     * - `wp_title` - Title retrieved for the currently displayed page, retrieved through
1143
     * `wp_title()`.
1144
     * - `body_class` - The body class retrieved through `get_body_class()`.
1145
     *
1146
     * The global context will be cached, which means that you can call this function again without
1147
     * losing performance.
1148
     *
1149
     * In addition to that, the context will contain template contexts depending on which template
1150
     * is being displayed. For archive templates, a `posts` variable will be present that will
1151
     * contain a collection of `Timber\Post` objects for the default query. For singular templates,
1152
     * a `post` variable will be present that that contains a `Timber\Post` object of the `$post`
1153
     * global.
1154
     *
1155
     * @api
1156
     * @since 2.0.0
1157
     *
1158
     * @param array $extra Any extra data to merge in. Overrides whatever is already there for this
1159
     *                     call only. In other words, the underlying context data is immutable and
1160
     *                     unaffected by passing this param.
1161
     *
1162
     * @return array An array of context variables that is used to pass into Twig templates through
1163
     *               a render or compile function.
1164
     */
1165
    public static function context(array $extra = [])
1166
    {
1167
        $context = self::context_global();
43✔
1168

1169
        if (\is_singular()) {
43✔
1170
            // NOTE: this also handles the is_front_page() case.
1171
            $context['post'] = Timber::get_post()->setup();
6✔
1172
        } elseif (\is_home()) {
37✔
1173
            $post = Timber::get_post();
4✔
1174

1175
            // When no page_on_front is set, there’s no post we can set up.
1176
            if ($post) {
4✔
1177
                $post->setup();
3✔
1178
            }
1179

1180
            $context['post'] = $post;
4✔
1181
            $context['posts'] = Timber::get_posts();
4✔
1182
        } elseif (\is_category() || \is_tag() || \is_tax()) {
33✔
1183
            $context['term'] = Timber::get_term();
3✔
1184
            $context['posts'] = Timber::get_posts();
3✔
1185
        } elseif (\is_search()) {
30✔
1186
            $context['posts'] = Timber::get_posts();
1✔
1187
            $context['search_query'] = \get_search_query();
1✔
1188
        } elseif (\is_author()) {
29✔
1189
            $context['author'] = Timber::get_user(\get_query_var('author'));
1✔
1190
            $context['posts'] = Timber::get_posts();
1✔
1191
        } elseif (\is_archive()) {
28✔
1192
            $context['posts'] = Timber::get_posts();
×
1193
        }
1194

1195
        return \array_merge($context, $extra);
43✔
1196
    }
1197

1198
    /**
1199
     * Gets the global context.
1200
     *
1201
     * This function is used by `Timber::context()` to get the global context. Usually, you don’t
1202
     * call this function directly, except when you need the global context in a partial view.
1203
     *
1204
     * The global context will be cached, which means that you can call this function again without
1205
     * losing performance.
1206
     *
1207
     * @api
1208
     * @since 2.0.0
1209
     * @example
1210
     * ```php
1211
     * add_shortcode( 'global_address', function() {
1212
     *     return Timber::compile(
1213
     *         'global_address.twig',
1214
     *         Timber::context_global()
1215
     *     );
1216
     * } );
1217
     * ```
1218
     *
1219
     * @return array An array of global context variables.
1220
     */
1221
    public static function context_global()
1222
    {
1223
        if (empty(self::$context_cache)) {
43✔
1224
            self::$context_cache['site'] = new Site();
43✔
1225
            self::$context_cache['theme'] = self::$context_cache['site']->theme;
43✔
1226
            self::$context_cache['user'] = \is_user_logged_in() ? static::get_user() : false;
43✔
1227

1228
            self::$context_cache['http_host'] = URLHelper::get_scheme() . '://' . URLHelper::get_host();
43✔
1229
            self::$context_cache['wp_title'] = Helper::get_wp_title();
43✔
1230
            self::$context_cache['body_class'] = \implode(' ', \get_body_class());
43✔
1231

1232
            /**
1233
             * Filters the global Timber context.
1234
             *
1235
             * By using this filter, you can add custom data to the global Timber context, which
1236
             * means that this data will be available on every page that is initialized with
1237
             * `Timber::context()`.
1238
             *
1239
             * Be aware that data will be cached as soon as you call `Timber::context()` for the
1240
             * first time. That’s why you should add this filter before you call
1241
             * `Timber::context()`.
1242
             *
1243
             * @see \Timber\Timber::context()
1244
             * @since 0.21.7
1245
             * @example
1246
             * ```php
1247
             * add_filter( 'timber/context', function( $context ) {
1248
             *     // Example: A custom value
1249
             *     $context['custom_site_value'] = 'Hooray!';
1250
             *
1251
             *     // Example: Add a menu to the global context.
1252
             *     $context['menu'] = new \Timber\Menu( 'primary-menu' );
1253
             *
1254
             *     // Example: Add all ACF options to global context.
1255
             *     $context['options'] = get_fields( 'options' );
1256
             *
1257
             *     return $context;
1258
             * } );
1259
             * ```
1260
             * ```twig
1261
             * <h1>{{ custom_site_value|e }}</h1>
1262
             *
1263
             * {% for item in menu.items %}
1264
             *     {# Display menu item #}
1265
             * {% endfor %}
1266
             *
1267
             * <footer>
1268
             *     {% if options.footer_text is not empty %}
1269
             *         {{ options.footer_text|e }}
1270
             *     {% endif %}
1271
             * </footer>
1272
             * ```
1273
             *
1274
             * @param array $context The global context.
1275
             */
1276
            self::$context_cache = \apply_filters('timber/context', self::$context_cache);
43✔
1277

1278
            /**
1279
             * Filters the global Timber context.
1280
             *
1281
             * @deprecated 2.0.0, use `timber/context`
1282
             */
1283
            self::$context_cache = \apply_filters_deprecated(
43✔
1284
                'timber_context',
43✔
1285
                [self::$context_cache],
43✔
1286
                '2.0.0',
43✔
1287
                'timber/context'
43✔
1288
            );
43✔
1289
        }
1290

1291
        return self::$context_cache;
43✔
1292
    }
1293

1294
    /**
1295
     * Compiles a Twig file.
1296
     *
1297
     * Passes data to a Twig file and returns the output. If the template file doesn't exist it will throw a warning
1298
     * when WP_DEBUG is enabled.
1299
     *
1300
     * @api
1301
     * @example
1302
     * ```php
1303
     * $data = array(
1304
     *     'firstname' => 'Jane',
1305
     *     'lastname' => 'Doe',
1306
     *     'email' => 'jane.doe@example.org',
1307
     * );
1308
     *
1309
     * $team_member = Timber::compile( 'team-member.twig', $data );
1310
     * ```
1311
     * @param array|string $filenames  Name or full path of the Twig file to compile. If this is an array of file
1312
     *                                 names or paths, Timber will compile the first file that exists.
1313
     * @param array        $data       Optional. An array of data to use in Twig template.
1314
     * @param bool|int     $expires    Optional. In seconds. Use false to disable cache altogether. When passed an
1315
     *                                 array, the first value is used for non-logged in visitors, the second for users.
1316
     *                                 Default false.
1317
     * @param string       $cache_mode Optional. Any of the cache mode constants defined in Timber\Loader.
1318
     * @param bool         $via_render Optional. Whether to apply optional render or compile filters. Default false.
1319
     * @return bool|string The returned output.
1320
     */
1321
    public static function compile($filenames, $data = [], $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT, $via_render = false)
1322
    {
1323
        if (!\defined('TIMBER_LOADED')) {
81✔
1324
            self::init();
×
1325
        }
1326
        $caller = LocationManager::get_calling_script_dir(1);
81✔
1327
        $loader = new Loader($caller);
81✔
1328
        $file = $loader->choose_template($filenames);
81✔
1329

1330
        $caller_file = LocationManager::get_calling_script_file(1);
81✔
1331

1332
        /**
1333
         * Fires after the calling PHP file was determined in Timber’s compile
1334
         * function.
1335
         *
1336
         * This action is used by the Timber Debug Bar extension.
1337
         *
1338
         * @since 1.1.2
1339
         * @since 2.0.0 Switched from filter to action.
1340
         *
1341
         * @param string|null $caller_file The calling script file.
1342
         */
1343
        \do_action('timber/calling_php_file', $caller_file);
81✔
1344

1345
        if ($via_render) {
81✔
1346
            /**
1347
             * Filters the Twig template that should be rendered.
1348
             *
1349
             * @since 2.0.0
1350
             *
1351
             * @param string $file The chosen Twig template name to render.
1352
             */
1353
            $file = \apply_filters('timber/render/file', $file);
3✔
1354

1355
            /**
1356
             * Filters the Twig file that should be rendered.
1357
             *
1358
             * @codeCoverageIgnore
1359
             * @deprecated 2.0.0, use `timber/render/file`
1360
             */
1361
            $file = \apply_filters_deprecated(
3✔
1362
                'timber_render_file',
3✔
1363
                [$file],
3✔
1364
                '2.0.0',
3✔
1365
                'timber/render/file'
3✔
1366
            );
3✔
1367
        } else {
1368
            /**
1369
             * Filters the Twig template that should be compiled.
1370
             *
1371
             * @since 2.0.0
1372
             *
1373
             * @param string $file The chosen Twig template name to compile.
1374
             */
1375
            $file = \apply_filters('timber/compile/file', $file);
78✔
1376

1377
            /**
1378
             * Filters the Twig template that should be compiled.
1379
             *
1380
             * @deprecated 2.0.0
1381
             */
1382
            $file = \apply_filters_deprecated(
78✔
1383
                'timber_compile_file',
78✔
1384
                [$file],
78✔
1385
                '2.0.0',
78✔
1386
                'timber/compile/file'
78✔
1387
            );
78✔
1388
        }
1389
        $output = false;
81✔
1390

1391
        if ($file !== false) {
81✔
1392
            if (\is_null($data)) {
79✔
1393
                $data = [];
2✔
1394
            }
1395

1396
            if ($via_render) {
79✔
1397
                /**
1398
                 * Filters the data that should be passed for rendering a Twig template.
1399
                 *
1400
                 * @since 2.0.0
1401
                 *
1402
                 * @param array  $data The data that is used to render the Twig template.
1403
                 * @param string $file The name of the Twig template to render.
1404
                 */
1405
                $data = \apply_filters('timber/render/data', $data, $file);
3✔
1406
                /**
1407
                 * Filters the data that should be passed for rendering a Twig template.
1408
                 *
1409
                 * @codeCoverageIgnore
1410
                 * @deprecated 2.0.0
1411
                 */
1412
                $data = \apply_filters_deprecated(
3✔
1413
                    'timber_render_data',
3✔
1414
                    [$data],
3✔
1415
                    '2.0.0',
3✔
1416
                    'timber/render/data'
3✔
1417
                );
3✔
1418
            } else {
1419
                /**
1420
                 * Filters the data that should be passed for compiling a Twig template.
1421
                 *
1422
                 * @since 2.0.0
1423
                 *
1424
                 * @param array  $data The data that is used to compile the Twig template.
1425
                 * @param string $file The name of the Twig template to compile.
1426
                 */
1427
                $data = \apply_filters('timber/compile/data', $data, $file);
76✔
1428

1429
                /**
1430
                 * Filters the data that should be passed for compiling a Twig template.
1431
                 *
1432
                 * @deprecated 2.0.0, use `timber/compile/data`
1433
                 */
1434
                $data = \apply_filters_deprecated(
76✔
1435
                    'timber_compile_data',
76✔
1436
                    [$data],
76✔
1437
                    '2.0.0',
76✔
1438
                    'timber/compile/data'
76✔
1439
                );
76✔
1440
            }
1441

1442
            $output = $loader->render($file, $data, $expires, $cache_mode);
79✔
1443
        } else {
1444
            if (\is_array($filenames)) {
2✔
1445
                $filenames = \implode(", ", $filenames);
1✔
1446
            }
1447
            Helper::error_log('Error loading your template files: ' . $filenames . '. Make sure one of these files exists.');
2✔
1448
        }
1449

1450
        /**
1451
         * Filters the compiled result before it is returned in `Timber::compile()`.
1452
         *
1453
         * It adds the posibility to filter the output ready for render.
1454
         *
1455
         * @since 2.0.0
1456
         *
1457
         * @param string $output
1458
         */
1459
        $output = \apply_filters('timber/compile/result', $output);
79✔
1460

1461
        /**
1462
         * Fires after a Twig template was compiled and before the compiled data
1463
         * is returned.
1464
         *
1465
         * This action can be helpful if you need to debug Twig template
1466
         * compilation.
1467
         *
1468
         * @todo Add parameter descriptions
1469
         *
1470
         * @since 2.0.0
1471
         *
1472
         * @param string $output
1473
         * @param string $file
1474
         * @param array  $data
1475
         * @param bool   $expires
1476
         * @param string $cache_mode
1477
         */
1478
        \do_action('timber/compile/done', $output, $file, $data, $expires, $cache_mode);
79✔
1479

1480
        /**
1481
         * Fires after a Twig template was compiled and before the compiled data
1482
         * is returned.
1483
         *
1484
         * @deprecated 2.0.0, use `timber/compile/done`
1485
         */
1486
        \do_action_deprecated('timber_compile_done', [], '2.0.0', 'timber/compile/done');
79✔
1487

1488
        return $output;
79✔
1489
    }
1490

1491
    /**
1492
     * Compile a string.
1493
     *
1494
     * @api
1495
     * @example
1496
     * ```php
1497
     * $data = array(
1498
     *     'username' => 'Jane Doe',
1499
     * );
1500
     *
1501
     * $welcome = Timber::compile_string( 'Hi {{ username }}, I’m a string with a custom Twig variable', $data );
1502
     * ```
1503
     * @param string $string A string with Twig variables.
1504
     * @param array  $data   Optional. An array of data to use in Twig template.
1505
     * @return bool|string
1506
     */
1507
    public static function compile_string($string, $data = [])
1508
    {
1509
        $dummy_loader = new Loader();
323✔
1510
        $twig = $dummy_loader->get_twig();
323✔
1511
        $template = $twig->createTemplate($string);
323✔
1512
        return $template->render($data);
321✔
1513
    }
1514

1515
    /**
1516
     * Fetch function.
1517
     *
1518
     * @api
1519
     * @deprecated 2.0.0 use Timber::compile()
1520
     * @param array|string $filenames  Name of the Twig file to render. If this is an array of files, Timber will
1521
     *                                 render the first file that exists.
1522
     * @param array        $data       Optional. An array of data to use in Twig template.
1523
     * @param bool|int     $expires    Optional. In seconds. Use false to disable cache altogether. When passed an
1524
     *                                 array, the first value is used for non-logged in visitors, the second for users.
1525
     *                                 Default false.
1526
     * @param string       $cache_mode Optional. Any of the cache mode constants defined in Timber\Loader.
1527
     * @return bool|string The returned output.
1528
     */
1529
    public static function fetch($filenames, $data = [], $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT)
1530
    {
1531
        Helper::deprecated(
×
1532
            'fetch',
×
1533
            'Timber::compile() (see https://timber.github.io/docs/reference/timber/#compile for more information)',
×
1534
            '2.0.0'
×
1535
        );
×
1536
        $output = self::compile($filenames, $data, $expires, $cache_mode, true);
×
1537

1538
        /**
1539
         * Filters the compiled result before it is returned.
1540
         *
1541
         * @see \Timber\Timber::fetch()
1542
         * @since 0.16.7
1543
         * @deprecated 2.0.0 use timber/compile/result
1544
         *
1545
         * @param string $output The compiled output.
1546
         */
1547
        $output = \apply_filters_deprecated(
×
1548
            'timber_compile_result',
×
1549
            [$output],
×
1550
            '2.0.0',
×
1551
            'timber/compile/result'
×
1552
        );
×
1553

1554
        return $output;
×
1555
    }
1556

1557
    /**
1558
     * Renders a Twig file.
1559
     *
1560
     * Passes data to a Twig file and echoes the output.
1561
     *
1562
     * @api
1563
     * @example
1564
     * ```php
1565
     * $context = Timber::context();
1566
     *
1567
     * Timber::render( 'index.twig', $context );
1568
     * ```
1569
     * @param array|string $filenames  Name or full path of the Twig file to render. If this is an array of file
1570
     *                                 names or paths, Timber will render the first file that exists.
1571
     * @param array        $data       Optional. An array of data to use in Twig template.
1572
     * @param bool|int     $expires    Optional. In seconds. Use false to disable cache altogether. When passed an
1573
     *                                 array, the first value is used for non-logged in visitors, the second for users.
1574
     *                                 Default false.
1575
     * @param string       $cache_mode Optional. Any of the cache mode constants defined in Timber\Loader.
1576
     */
1577
    public static function render($filenames, $data = [], $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT)
1578
    {
1579
        $output = self::compile($filenames, $data, $expires, $cache_mode, true);
3✔
1580
        echo $output;
3✔
1581
    }
1582

1583
    /**
1584
     * Render a string with Twig variables.
1585
     *
1586
     * @api
1587
     * @example
1588
     * ```php
1589
     * $data = array(
1590
     *     'username' => 'Jane Doe',
1591
     * );
1592
     *
1593
     * Timber::render_string( 'Hi {{ username }}, I’m a string with a custom Twig variable', $data );
1594
     * ```
1595
     * @param string $string A string with Twig variables.
1596
     * @param array  $data   An array of data to use in Twig template.
1597
     */
1598
    public static function render_string($string, $data = [])
1599
    {
1600
        $compiled = self::compile_string($string, $data);
2✔
1601
        echo $compiled;
2✔
1602
    }
1603

1604

1605
    /*  Sidebar
1606
    ================================ */
1607

1608
    /**
1609
     * Get sidebar.
1610
     * @api
1611
     * @param string  $sidebar
1612
     * @param array   $data
1613
     * @return bool|string
1614
     */
1615
    public static function get_sidebar($sidebar = 'sidebar.php', $data = [])
1616
    {
1617
        if (\strstr(\strtolower($sidebar), '.php')) {
2✔
1618
            return self::get_sidebar_from_php($sidebar, $data);
1✔
1619
        }
1620
        return self::compile($sidebar, $data);
1✔
1621
    }
1622

1623
    /**
1624
     * Get sidebar from PHP
1625
     * @api
1626
     * @param string  $sidebar
1627
     * @param array   $data
1628
     * @return string
1629
     */
1630
    public static function get_sidebar_from_php($sidebar = '', $data = [])
1631
    {
1632
        $caller = LocationManager::get_calling_script_dir(1);
1✔
1633
        $uris = LocationManager::get_locations($caller);
1✔
1634
        \ob_start();
1✔
1635
        $found = false;
1✔
1636
        foreach ($uris as $namespace => $uri_locations) {
1✔
1637
            if (\is_array($uri_locations)) {
1✔
1638
                foreach ($uri_locations as $uri) {
1✔
1639
                    if (\file_exists(\trailingslashit($uri) . $sidebar)) {
1✔
1640
                        include \trailingslashit($uri) . $sidebar;
1✔
1641
                        $found = true;
1✔
1642
                    }
1643
                }
1644
            }
1645
        }
1646
        if (!$found) {
1✔
1647
            Helper::error_log('error loading your sidebar, check to make sure the file exists');
×
1648
        }
1649
        $ret = \ob_get_contents();
1✔
1650
        \ob_end_clean();
1✔
1651

1652
        return $ret;
1✔
1653
    }
1654

1655
    /**
1656
     * Get widgets.
1657
     *
1658
     * @api
1659
     * @param int|string $widget_id Optional. Index, name or ID of dynamic sidebar. Default 1.
1660
     * @return string
1661
     */
1662
    public static function get_widgets($widget_id)
1663
    {
1664
        return \trim(Helper::ob_function('dynamic_sidebar', [$widget_id]));
2✔
1665
    }
1666

1667
    /**
1668
     * Get pagination.
1669
     *
1670
     * @api
1671
     * @deprecated 2.0.0
1672
     * @link https://timber.github.io/docs/guides/pagination/
1673
     * @param array $prefs an array of preference data.
1674
     * @return array|mixed
1675
     */
1676
    public static function get_pagination($prefs = [])
1677
    {
1678
        Helper::deprecated(
16✔
1679
            'get_pagination',
16✔
1680
            '{{ posts.pagination }} (see https://timber.github.io/docs/guides/pagination/ for more information)',
16✔
1681
            '2.0.0'
16✔
1682
        );
16✔
1683

1684
        return Pagination::get_pagination($prefs);
16✔
1685
    }
1686
}
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

© 2025 Coveralls, Inc