• 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

78.18
/src/Helper.php
1
<?php
2

3
namespace Timber;
4

5
use Exception;
6
use InvalidArgumentException;
7
use stdClass;
8
use Timber\Factory\PostFactory;
9
use WP_List_Util;
10
use WP_Post;
11
use WP_Term;
12
use WP_User;
13

14
/**
15
 * Class Helper
16
 *
17
 * As the name suggests these are helpers for Timber (and you!) when developing. You can find additional
18
 * (mainly internally-focused helpers) in Timber\URLHelper.
19
 * @api
20
 */
21
class Helper
22
{
23
    /**
24
     * A utility for a one-stop shop for transients.
25
     *
26
     * @api
27
     * @example
28
     * ```php
29
     * $context = Timber::context( [
30
     *     'favorites' => Timber\Helper::transient( 'user-' . $uid . '-favorites' , function() use ( $uid ) {
31
     *          // Some expensive query here that’s doing something you want to store to a transient.
32
     *          return $favorites;
33
     *     }, 600 ),
34
     * ] );
35
     *
36
     * Timber::render('single.twig', $context);
37
     * ```
38
     *
39
     * @param string      $slug           Unique identifier for transient
40
     * @param callable     $callback      Callback that generates the data that's to be cached
41
     * @param integer      $transient_time (optional) Expiration of transients in seconds
42
     * @param integer     $lock_timeout   (optional) How long (in seconds) to lock the transient to prevent race conditions
43
     * @param boolean     $force          (optional) Force callback to be executed when transient is locked
44
     *
45
     * @return mixed
46
     */
47
    public static function transient($slug, $callback, $transient_time = 0, $lock_timeout = 5, $force = false)
48
    {
49
        /**
50
         * Filters the transient slug.
51
         *
52
         * This might be useful if you are using a multilingual solution.
53
         *
54
         * @since 0.22.6
55
         *
56
         * @param string $slug The slug for the transient.
57
         */
58
        $slug = \apply_filters('timber/transient/slug', $slug);
11✔
59

60
        $enable_transients = ($transient_time === false || (\defined('WP_DISABLE_TRANSIENTS') && WP_DISABLE_TRANSIENTS)) ? false : true;
11✔
61
        $data = $enable_transients ? \get_transient($slug) : false;
11✔
62

63
        if (false === $data) {
11✔
64
            $data = self::handle_transient_locking($slug, $callback, $transient_time, $lock_timeout, $force, $enable_transients);
11✔
65
        }
66
        return $data;
11✔
67
    }
68

69
    /**
70
     * Does the dirty work of locking the transient, running the callback and unlocking.
71
     *
72
     * @internal
73
     *
74
     * @param string     $slug
75
     * @param callable     $callback
76
     * @param integer      $transient_time Expiration of transients in seconds
77
     * @param integer     $lock_timeout   How long (in seconds) to lock the transient to prevent race conditions
78
     * @param boolean     $force          Force callback to be executed when transient is locked
79
     * @param boolean     $enable_transients Force callback to be executed when transient is locked
80
     */
81
    protected static function handle_transient_locking($slug, $callback, $transient_time, $lock_timeout, $force, $enable_transients)
82
    {
83
        if ($enable_transients && self::_is_transient_locked($slug)) {
11✔
84
            /**
85
             * Filters …
86
             *
87
             * @todo Add summary, add description, add description for $force param
88
             *
89
             * @since 2.0.0
90
             * @param bool $force
91
             */
92
            $force = \apply_filters('timber/transient/force_transients', $force);
5✔
93

94
            /**
95
             * Filters …
96
             *
97
             * @todo Add summary
98
             *
99
             * @deprecated 2.0.0, use `timber/transient/force_transients`
100
             */
101
            $force = \apply_filters_deprecated(
5✔
102
                'timber_force_transients',
5✔
103
                [$force],
5✔
104
                '2.0.0',
5✔
105
                'timber/transient/force_transients'
5✔
106
            );
5✔
107

108
            /**
109
             * Filters …
110
             *
111
             * Here is a description about the filter.
112
             * `$slug` The transient slug.
113
             *
114
             * @todo Add summary, add description, add description for $force param
115
             *
116
             * @since 2.0.0
117
             *
118
             * @param bool $force
119
             */
120
            $force = \apply_filters("timber/transient/force_transient_{$slug}", $force);
5✔
121

122
            /**
123
             * Filters …
124
             *
125
             * @todo Add summary
126
             *
127
             * @deprecated 2.0.0, use `timber/transient/force_transient_{$slug}`
128
             */
129
            $force = \apply_filters("timber_force_transient_{$slug}", $force);
5✔
130

131
            if (!$force) {
5✔
132
                //the server is currently executing the process.
133
                //We're just gonna dump these users. Sorry!
134
                return false;
2✔
135
            }
136
            $enable_transients = false;
3✔
137
        }
138
        // lock timeout shouldn't be higher than 5 seconds, unless
139
        // remote calls with high timeouts are made here
140
        if ($enable_transients) {
9✔
141
            self::_lock_transient($slug, $lock_timeout);
6✔
142
        }
143
        $data = $callback();
9✔
144
        if ($enable_transients) {
9✔
145
            \set_transient($slug, $data, $transient_time);
6✔
146
            self::_unlock_transient($slug);
6✔
147
        }
148
        return $data;
9✔
149
    }
150

151
    /**
152
     * @internal
153
     * @param string $slug
154
     * @param integer $lock_timeout
155
     */
156
    public static function _lock_transient($slug, $lock_timeout)
157
    {
158
        \set_transient($slug . '_lock', true, $lock_timeout);
14✔
159
    }
160

161
    /**
162
     * @internal
163
     * @param string $slug
164
     */
165
    public static function _unlock_transient($slug)
166
    {
167
        \delete_transient($slug . '_lock');
7✔
168
    }
169

170
    /**
171
     * @internal
172
     * @param string $slug
173
     */
174
    public static function _is_transient_locked($slug)
175
    {
176
        return (bool) \get_transient($slug . '_lock');
14✔
177
    }
178

179
    /* These are for measuring page render time */
180

181
    /**
182
     * For measuring time, this will start a timer.
183
     *
184
     * @api
185
     * @return float
186
     */
187
    public static function start_timer()
188
    {
189
        $time = \microtime();
1✔
190
        $time = \explode(' ', $time);
1✔
191
        $time = (float) $time[1] + (float) $time[0];
1✔
192
        return $time;
1✔
193
    }
194

195
    /**
196
     * For stopping time and getting the data.
197
     *
198
     * @api
199
     * @example
200
     * ```php
201
     * $start = Timber\Helper::start_timer();
202
     * // do some stuff that takes awhile
203
     * echo Timber\Helper::stop_timer( $start );
204
     * ```
205
     *
206
     * @param int     $start
207
     * @return string
208
     */
209
    public static function stop_timer($start)
210
    {
211
        $time = \microtime();
1✔
212
        $time = \explode(' ', $time);
1✔
213
        $time = (float) $time[1] + (float) $time[0];
1✔
214
        $finish = $time;
1✔
215
        $total_time = \round(($finish - $start), 4);
1✔
216
        return $total_time . ' seconds.';
1✔
217
    }
218

219
    /* Function Utilities
220
    ======================== */
221

222
    /**
223
     * Calls a function with an output buffer. This is useful if you have a function that outputs
224
     * text that you want to capture and use within a twig template.
225
     *
226
     * @api
227
     * @example
228
     * ```php
229
     * function the_form() {
230
     *     echo '<form action="form.php"><input type="text" /><input type="submit /></form>';
231
     * }
232
     *
233
     * $context = Timber::context( [
234
     *     'form' => Timber\Helper::ob_function( 'the_form' ),
235
     * ] );
236
     *
237
     * Timber::render('single-form.twig', $context);
238
     * ```
239
     * ```twig
240
     * <h1>{{ post.title }}</h1>
241
     * {{ my_form }}
242
     * ```
243
     * ```html
244
     * <h1>Apply to my contest!</h1>
245
     * <form action="form.php"><input type="text" /><input type="submit /></form>
246
     * ```
247
     *
248
     * @param callable $function
249
     * @param array    $args
250
     *
251
     * @return string
252
     */
253
    public static function ob_function($function, $args = [null])
254
    {
255
        \ob_start();
9✔
256
        \call_user_func_array($function, $args);
9✔
257
        return \ob_get_clean();
9✔
258
    }
259

260
    /**
261
     * Output a value (string, array, object, etc.) to the error log
262
     *
263
     * @api
264
     * @param mixed $error The error that you want to error_log().
265
     * @return void
266
     */
267
    public static function error_log($error)
268
    {
269
        global $timber_disable_error_log;
270
        if (!WP_DEBUG || $timber_disable_error_log) {
5✔
271
            return;
2✔
272
        }
273
        if (\is_object($error) || \is_array($error)) {
3✔
274
            $error = \print_r($error, true);
1✔
275
        }
276
        return \error_log('[ Timber ] ' . $error);
3✔
277
    }
278

279
    /**
280
     * Trigger a warning.
281
     *
282
     * @api
283
     *
284
     * @param string $message The warning that you want to output.
285
     *
286
     * @return void
287
     */
288
    public static function warn($message)
289
    {
290
        if (!WP_DEBUG) {
1✔
291
            return;
×
292
        }
293

294
        \trigger_error($message, E_USER_WARNING);
1✔
295
    }
296

297
    /**
298
     * Marks something as being incorrectly called.
299
     *
300
     * There is a hook 'doing_it_wrong_run' that will be called that can be used
301
     * to get the backtrace up to what file and function called the deprecated
302
     * function.
303
     *
304
     * The current behavior is to trigger a user error if `WP_DEBUG` is true.
305
     *
306
     * If you want to catch errors like these in tests, then add the @expectedIncorrectUsage tag.
307
     * E.g.: "@expectedIncorrectUsage Timber::get_posts()".
308
     *
309
     * @api
310
     * @since 2.0.0
311
     * @since WordPress 3.1.0
312
     * @see \_doing_it_wrong()
313
     *
314
     * @param string $function The function that was called.
315
     * @param string $message  A message explaining what has been done incorrectly.
316
     * @param string $version  The version of Timber where the message was added.
317
     */
318
    public static function doing_it_wrong($function, $message, $version)
319
    {
320
        /**
321
         * Fires when the given function is being used incorrectly.
322
         *
323
         * @param string $function The function that was called.
324
         * @param string $message  A message explaining what has been done incorrectly.
325
         * @param string $version  The version of WordPress where the message was added.
326
         */
327
        \do_action('doing_it_wrong_run', $function, $message, $version);
14✔
328

329
        if (!WP_DEBUG) {
14✔
330
            return;
×
331
        }
332

333
        /**
334
         * Filters whether to trigger an error for _doing_it_wrong() calls.
335
         *
336
         * This filter is mainly used by unit tests.
337
         *
338
         * @since WordPress 3.1.0
339
         * @since WordPress 5.1.0 Added the $function, $message and $version parameters.
340
         *
341
         * @param bool   $trigger  Whether to trigger the error for _doing_it_wrong() calls. Default true.
342
         * @param string $function The function that was called.
343
         * @param string $message  A message explaining what has been done incorrectly.
344
         * @param string $version  The version of WordPress where the message was added.
345
         */
346
        $should_trigger_error = \apply_filters(
14✔
347
            'doing_it_wrong_trigger_error',
14✔
348
            true,
14✔
349
            $function,
14✔
350
            $message,
14✔
351
            $version
14✔
352
        );
14✔
353

354
        if ($should_trigger_error) {
14✔
355
            if (\is_null($version)) {
×
356
                $version = '';
×
357
            } else {
358
                $version = \sprintf(
×
359
                    '(This message was added in Timber version %s.)',
×
360
                    $version
×
361
                );
×
362
            }
363

364
            $message .= \sprintf(
×
365
                ' Please see Debugging in WordPress (%1$s) as well as Debugging in Timber (%2$s) for more information.',
×
366
                'https://wordpress.org/support/article/debugging-in-wordpress/',
×
367
                'https://timber.github.io/docs/guides/debugging/'
×
368
            );
×
369

370
            $error_message = \sprintf(
×
371
                '%1$s was called <strong>incorrectly</strong>. %2$s %3$s',
×
372
                $function,
×
373
                $message,
×
374
                $version
×
375
            );
×
376

377
            // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
378
            // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
379
            \trigger_error('[ Timber ] ' . $error_message);
×
380
        }
381
    }
382

383
    /**
384
     * Triggers a deprecation warning.
385
     *
386
     * If you want to catch errors like these in tests, then add the @expectedDeprecated tag to the
387
     * DocBlock. E.g.: "@expectedDeprecated {{ TimberImage() }}".
388
     *
389
     * @api
390
     * @see \_deprecated_function()
391
     *
392
     * @param string $function    The name of the deprecated function/method.
393
     * @param string $replacement The name of the function/method to use instead.
394
     * @param string $version     The version of Timber when the function was deprecated.
395
     *
396
     * @return void
397
     */
398
    public static function deprecated($function, $replacement, $version)
399
    {
400
        /**
401
         * Fires when a deprecated function is being used.
402
         *
403
         * @param string $function    The function that was called.
404
         * @param string $replacement The name of the function/method to use instead.
405
         * @param string $version     The version of Timber where the message was added.
406
         */
407
        \do_action('deprecated_function_run', $function, $replacement, $version);
58✔
408

409
        if (!WP_DEBUG) {
58✔
410
            return;
×
411
        }
412

413
        /**
414
         * Filters whether to trigger an error for deprecated functions.
415
         *
416
         * @since WordPress 2.5.0
417
         *
418
         * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
419
         */
420
        if (!\apply_filters('deprecated_function_trigger_error', true)) {
58✔
421
            return;
58✔
422
        }
423

424
        if (!\is_null($replacement)) {
×
425
            $error_message = \sprintf(
×
426
                '%1$s is deprecated since Timber version %2$s! Use %3$s instead.',
×
427
                $function,
×
428
                $version,
×
429
                $replacement
×
430
            );
×
431
        } else {
432
            $error_message = \sprintf(
×
433
                '%1$s is deprecated since Timber version %2$s with no alternative available.',
×
434
                $function,
×
435
                $version
×
436
            );
×
437
        }
438

439
        // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
440
        // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
441
        \trigger_error('[ Timber ] ' . $error_message);
×
442
    }
443

444
    /**
445
     * @api
446
     *
447
     * @param string  $separator
448
     * @param string  $seplocation
449
     * @return string
450
     */
451
    public static function get_wp_title($separator = ' ', $seplocation = 'left')
452
    {
453
        /**
454
         * Filters the separator used for the page title.
455
         *
456
         * @since 2.0.0
457
         *
458
         * @param string $separator The separator to use. Default `' '`.
459
         */
460
        $separator = \apply_filters('timber/helper/wp_title_separator', $separator);
45✔
461

462
        /**
463
         * Filters the separator used for the page title.
464
         *
465
         * @deprecated 2.0.0, use `timber/helper/wp_title_separator`
466
         */
467
        $separator = \apply_filters_deprecated('timber_wp_title_seperator', [$separator], '2.0.0', 'timber/helper/wp_title_separator');
45✔
468

469
        return \trim(\wp_title($separator, false, $seplocation));
45✔
470
    }
471

472
    /**
473
     * Sorts object arrays by properties.
474
     *
475
     * @api
476
     *
477
     * @param array  $array The array of objects to sort.
478
     * @param string $prop  The property to sort by.
479
     *
480
     * @return void
481
     */
482
    public static function osort(&$array, $prop)
483
    {
484
        \usort($array, function ($a, $b) use ($prop) {
1✔
485
            return $a->$prop > $b->$prop ? 1 : -1;
1✔
486
        });
1✔
487
    }
488

489
    /**
490
     * @api
491
     *
492
     * @param array   $arr
493
     * @return bool
494
     */
495
    public static function is_array_assoc($arr)
496
    {
497
        if (!\is_array($arr)) {
1✔
498
            return false;
×
499
        }
500
        return (bool) \count(\array_filter(\array_keys($arr), 'is_string'));
1✔
501
    }
502

503
    /**
504
     * @api
505
     *
506
     * @param array   $array
507
     * @return stdClass
508
     */
509
    public static function array_to_object($array)
510
    {
511
        $obj = new stdClass();
2✔
512
        foreach ($array as $k => $v) {
2✔
513
            if (\is_array($v)) {
2✔
514
                $obj->{$k} = self::array_to_object($v); //RECURSION
1✔
515
            } else {
516
                $obj->{$k} = $v;
2✔
517
            }
518
        }
519
        return $obj;
2✔
520
    }
521

522
    /**
523
     * @api
524
     *
525
     * @param array   $array
526
     * @param string  $key
527
     * @param mixed   $value
528
     * @return bool|int
529
     */
530
    public static function get_object_index_by_property($array, $key, $value)
531
    {
532
        if (\is_array($array)) {
2✔
533
            $i = 0;
2✔
534
            foreach ($array as $arr) {
2✔
535
                if (\is_array($arr)) {
2✔
536
                    if ($arr[$key] == $value) {
1✔
537
                        return $i;
1✔
538
                    }
539
                } else {
540
                    if ($arr->$key == $value) {
1✔
541
                        return $i;
1✔
542
                    }
543
                }
544
                $i++;
2✔
545
            }
546
        }
547
        return false;
1✔
548
    }
549

550
    /**
551
     * @api
552
     *
553
     * @param array   $array
554
     * @param string  $key
555
     * @param mixed   $value
556
     * @return array|null
557
     * @throws Exception
558
     */
559
    public static function get_object_by_property($array, $key, $value)
560
    {
561
        if (\is_array($array)) {
3✔
562
            foreach ($array as $arr) {
2✔
563
                if ($arr->$key == $value) {
2✔
564
                    return $arr;
1✔
565
                }
566
            }
567
            return false;
1✔
568
        }
569
        throw new InvalidArgumentException('$array is not an array, got:');
1✔
570
    }
571

572
    /**
573
     * @api
574
     *
575
     * @param array   $array
576
     * @param int     $len
577
     * @return array
578
     */
579
    public static function array_truncate($array, $len)
580
    {
581
        if (\sizeof($array) > $len) {
1✔
582
            $array = \array_splice($array, 0, $len);
1✔
583
        }
584
        return $array;
1✔
585
    }
586

587
    /* Bool Utilities
588
    ======================== */
589

590
    /**
591
     * @api
592
     *
593
     * @param mixed   $value
594
     * @return bool
595
     */
596
    public static function is_true($value)
597
    {
598
        if (isset($value)) {
2✔
599
            if (\is_string($value)) {
2✔
600
                $value = \strtolower($value);
2✔
601
            }
602
            if (($value == 'true' || $value === 1 || $value === '1' || $value == true) && $value !== false && $value !== 'false') {
2✔
603
                return true;
2✔
604
            }
605
        }
606
        return false;
2✔
607
    }
608

609
    /**
610
     * Is the number even? Let's find out.
611
     *
612
     * @api
613
     *
614
     * @param int $i number to test.
615
     * @return bool
616
     */
617
    public static function iseven($i)
618
    {
619
        return ($i % 2) === 0;
1✔
620
    }
621

622
    /**
623
     * Is the number odd? Let's find out.
624
     *
625
     * @api
626
     *
627
     * @param int $i number to test.
628
     * @return bool
629
     */
630
    public static function isodd($i)
631
    {
632
        return ($i % 2) !== 0;
1✔
633
    }
634

635
    /**
636
     * Plucks the values of a certain key from an array of objects
637
     *
638
     * @api
639
     *
640
     * @param array  $array
641
     * @param string $key
642
     *
643
     * @return array
644
     */
645
    public static function pluck($array, $key)
646
    {
647
        $return = [];
4✔
648
        foreach ($array as $obj) {
4✔
649
            if (\is_object($obj) && \method_exists($obj, $key)) {
4✔
650
                $return[] = $obj->$key();
1✔
651
            } elseif (\is_object($obj) && \property_exists($obj, $key)) {
4✔
652
                $return[] = $obj->$key;
1✔
653
            } elseif (\is_array($obj) && isset($obj[$key])) {
3✔
654
                $return[] = $obj[$key];
2✔
655
            }
656
        }
657
        return $return;
4✔
658
    }
659

660
    /**
661
     * Filters a list of objects, based on a set of key => value arguments.
662
     * Uses WordPress WP_List_Util's filter.
663
     *
664
     * @api
665
     * @since 1.5.3
666
     * @ticket #1594
667
     *
668
     * @param array        $list to filter.
669
     * @param string|array $args to search for.
670
     * @param string       $operator to use (AND, NOT, OR).
671
     * @return array
672
     */
673
    public static function wp_list_filter($list, $args, $operator = 'AND')
674
    {
675
        if (!\is_array($args)) {
1✔
676
            $args = [
1✔
677
                'slug' => $args,
1✔
678
            ];
1✔
679
        }
680

681
        if (!\is_array($list) && !\is_a($list, 'Traversable')) {
1✔
682
            return [];
×
683
        }
684

685
        $util = new WP_List_Util($list);
1✔
686
        return $util->filter($args, $operator);
1✔
687
    }
688

689
    /**
690
     * Converts a WP object (WP_Post, WP_Term) into its
691
     * equivalent Timber class (Timber\Post, Timber\Term).
692
     *
693
     * If no match is found the function will return the inital argument.
694
     *
695
     * @param mixed $obj WP Object
696
     * @return mixed Instance of equivalent Timber object, or the argument if no match is found
697
     */
698
    public static function convert_wp_object($obj)
699
    {
700
        if ($obj instanceof WP_Post) {
17✔
701
            static $postFactory;
6✔
702
            $postFactory = $postFactory ?: new PostFactory();
6✔
703
            return $postFactory->from($obj->ID);
6✔
704
        } elseif ($obj instanceof WP_Term) {
13✔
705
            return Timber::get_term($obj->term_id);
1✔
706
        } elseif ($obj instanceof WP_User) {
13✔
707
            return Timber::get_user($obj->ID);
1✔
708
        }
709

710
        return $obj;
13✔
711
    }
712
}
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