• 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

62.09
/src/Term.php
1
<?php
2

3
namespace Timber;
4

5
use Timber\Factory\TermFactory;
6

7
use WP_Term;
8

9
/**
10
 * Class Term
11
 *
12
 * Terms: WordPress has got 'em, you want 'em. Categories. Tags. Custom Taxonomies. You don't care,
13
 * you're a fiend. Well let's get this under control:
14
 *
15
 * @api
16
 * @example
17
 * ```php
18
 * // Get a term by its ID
19
 * $context['term'] = Timber::get_term(6);
20
 *
21
 * // Get a term when on a term archive page
22
 * $context['term_page'] = Timber::get_term();
23
 *
24
 * // Get a term with a slug
25
 * $context['team'] = Timber::get_term('patriots');
26
 * Timber::render('index.twig', $context);
27
 * ```
28
 * ```twig
29
 * <h2>{{ term_page.name }} Archives</h2>
30
 * <h3>Teams</h3>
31
 * <ul>
32
 *     <li>{{ st_louis.name}} - {{ st_louis.description }}</li>
33
 *     <li>{{ team.name}} - {{ team.description }}</li>
34
 * </ul>
35
 * ```
36
 * ```html
37
 * <h2>Team Archives</h2>
38
 * <h3>Teams</h3>
39
 * <ul>
40
 *     <li>St. Louis Cardinals - Winner of 11 World Series</li>
41
 *     <li>New England Patriots - Winner of 6 Super Bowls</li>
42
 * </ul>
43
 * ```
44
 */
45
class Term extends CoreEntity
46
{
47
    /**
48
     * The underlying WordPress Core object.
49
     *
50
     * @since 2.0.0
51
     *
52
     * @var WP_Term|null
53
     */
54
    protected ?WP_Term $wp_object;
55

56
    public $object_type = 'term';
57

58
    public static $representation = 'term';
59

60
    public $_children;
61

62
    /**
63
     * @api
64
     * @var string the human-friendly name of the term (ex: French Cuisine)
65
     */
66
    public $name;
67

68
    /**
69
     * @api
70
     * @var string the WordPress taxonomy slug (ex: `post_tag` or `actors`)
71
     */
72
    public $taxonomy;
73

74
    /**
75
     * @internal
76
     */
77
    final protected function __construct()
78
    {
79
    }
99✔
80

81
    /**
82
     * @internal
83
     *
84
     * @param WP_Term      $wp_term The vanilla WordPress term object to build from.
85
     * @return \Timber\Term
86
     */
87
    public static function build(WP_Term $wp_term): self
88
    {
89
        $term = new static();
99✔
90
        $term->init($wp_term);
99✔
91
        return $term;
99✔
92
    }
93

94
    /**
95
     * The string the term will render as by default
96
     *
97
     * @api
98
     * @return string
99
     */
100
    public function __toString()
101
    {
102
        return $this->name;
2✔
103
    }
104

105
    /**
106
     *
107
     * @deprecated 2.0.0, use TermFactory::from instead.
108
     *
109
     * @param $tid
110
     * @param $taxonomy
111
     *
112
     * @return static
113
     */
114
    public static function from($tid, $taxonomy = null)
115
    {
116
        Helper::deprecated(
3✔
117
            "Term::from()",
3✔
118
            "Timber\Factory\TermFactory->from()",
3✔
119
            '2.0.0'
3✔
120
        );
3✔
121

122
        $termFactory = new TermFactory();
3✔
123
        return $termFactory->from($tid);
3✔
124
    }
125

126
    /* Setup
127
    ===================== */
128

129
    /**
130
     * @internal
131
     * @param WP_Term $term
132
     */
133
    protected function init(WP_Term $term)
134
    {
135
        $this->ID = $term->term_id;
99✔
136
        $this->id = $term->term_id;
99✔
137
        $this->wp_object = $term;
99✔
138
        $this->import($term);
99✔
139
    }
140

141
    /**
142
     * @internal
143
     * @param int|object|array $tid
144
     * @return mixed
145
     */
146
    protected function get_term($tid)
147
    {
148
        if (\is_object($tid) || \is_array($tid)) {
×
149
            return $tid;
×
150
        }
151
        $tid = self::get_tid($tid);
×
152

153
        if (\is_array($tid)) {
×
154
            //there's more than one matching $term_id, let's figure out which is correct
155
            if (isset($this->taxonomy) && \strlen($this->taxonomy)) {
×
156
                foreach ($tid as $term_id) {
×
157
                    $maybe_term = \get_term($term_id, $this->taxonomy);
×
158
                    if ($maybe_term) {
×
159
                        return $maybe_term;
×
160
                    }
161
                }
162
            }
163
            $tid = $tid[0];
×
164
        }
165

166
        if (isset($this->taxonomy) && \strlen($this->taxonomy)) {
×
167
            return \get_term($tid, $this->taxonomy);
×
168
        } else {
169
            global $wpdb;
170
            $query = $wpdb->prepare("SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d LIMIT 1", $tid);
×
171
            $tax = $wpdb->get_var($query);
×
172
            if (isset($tax) && \strlen($tax)) {
×
173
                $this->taxonomy = $tax;
×
174
                return \get_term($tid, $tax);
×
175
            }
176
        }
177
        return null;
×
178
    }
179

180
    /**
181
     * @internal
182
     * @param mixed $tid
183
     * @return int|array
184
     */
185
    protected static function get_tid($tid)
186
    {
187
        global $wpdb;
188
        if (\is_numeric($tid)) {
×
189
            return $tid;
×
190
        }
191
        if (\gettype($tid) === 'object') {
×
192
            $tid = $tid->term_id;
×
193
        }
194
        if (\is_numeric($tid)) {
×
195
            $query = $wpdb->prepare("SELECT term_id FROM $wpdb->terms WHERE term_id = %d", $tid);
×
196
        } else {
197
            $query = $wpdb->prepare("SELECT term_id FROM $wpdb->terms WHERE slug = %s", $tid);
×
198
        }
199
        $result = $wpdb->get_col($query);
×
200
        if ($result) {
×
201
            if (\count($result) == 1) {
×
202
                return $result[0];
×
203
            }
204
            return $result;
×
205
        }
206
        return false;
×
207
    }
208

209

210
    /* Public methods
211
    ===================== */
212

213
    /**
214
     * Gets the underlying WordPress Core object.
215
     *
216
     * @since 2.0.0
217
     *
218
     * @return WP_Term|null
219
     */
220
    public function wp_object(): ?WP_Term
221
    {
222
        return $this->wp_object;
1✔
223
    }
224

225
    /**
226
     * @deprecated 2.0.0, use `{{ term.edit_link }}` instead.
227
     * @return string
228
     */
229
    public function get_edit_url()
230
    {
231
        Helper::deprecated('{{ term.get_edit_url }}', '{{ term.edit_link }}', '2.0.0');
×
232
        return $this->edit_link();
×
233
    }
234

235
    /**
236
     * Gets a term meta value.
237
     * @deprecated 2.0.0, use `{{ term.meta('field_name') }}` instead.
238
     *
239
     * @param string $field_name The field name for which you want to get the value.
240
     * @return string The meta field value.
241
     */
242
    public function get_meta_field($field_name)
243
    {
244
        Helper::deprecated(
×
245
            "{{ term.get_meta_field('field_name') }}",
×
246
            "{{ term.meta('field_name') }}",
×
247
            '2.0.0'
×
248
        );
×
249
        return $this->meta($field_name);
×
250
    }
251

252
    /**
253
     * @internal
254
     * @return array
255
     */
256
    public function children()
257
    {
258
        if (!isset($this->_children)) {
1✔
259
            $children = \get_term_children($this->ID, $this->taxonomy);
1✔
260
            foreach ($children as &$child) {
1✔
261
                $child = Timber::get_term($child);
1✔
262
            }
263
            $this->_children = $children;
1✔
264
        }
265
        return $this->_children;
1✔
266
    }
267

268
    /**
269
     * Return the description of the term
270
     *
271
     * @api
272
     * @return string
273
     */
274
    public function description()
275
    {
276
        $prefix = '<p>';
1✔
277
        $desc = \term_description($this->ID, $this->taxonomy);
1✔
278
        if (\substr($desc, 0, \strlen($prefix)) == $prefix) {
1✔
279
            $desc = \substr($desc, \strlen($prefix));
1✔
280
        }
281
        $desc = \preg_replace('/' . \preg_quote('</p>', '/') . '$/', '', $desc);
1✔
282
        return \trim($desc);
1✔
283
    }
284

285
    /**
286
     * Checks whether the current user can edit the term.
287
     *
288
     * @api
289
     * @example
290
     * ```twig
291
     * {% if term.can_edit %}
292
     *     <a href="{{ term.edit_link }}">Edit</a>
293
     * {% endif %}
294
     * ```
295
     * @return bool
296
     */
297
    public function can_edit(): bool
298
    {
299
        return \current_user_can('edit_term', $this->ID);
2✔
300
    }
301

302
    /**
303
     * Gets the edit link for a term if the current user has the correct rights.
304
     *
305
     * @api
306
     * @example
307
     * ```twig
308
     * {% if term.can_edit %}
309
     *    <a href="{{ term.edit_link }}">Edit</a>
310
     * {% endif %}
311
     * ```
312
     * @return string|null The edit URL of a term in the WordPress admin or null if the current user can’t edit the
313
     *                     term.
314
     */
315
    public function edit_link(): ?string
316
    {
317
        if (!$this->can_edit()) {
1✔
318
            return null;
×
319
        }
320

321
        return \get_edit_term_link($this->ID, $this->taxonomy);
1✔
322
    }
323

324
    /**
325
     * Returns a full link to the term archive page like `http://example.com/category/news`
326
     *
327
     * @api
328
     * @example
329
     * ```twig
330
     * See all posts in: <a href="{{ term.link }}">{{ term.name }}</a>
331
     * ```
332
     *
333
     * @return string
334
     */
335
    public function link()
336
    {
337
        $link = \get_term_link($this->wp_object);
3✔
338

339
        /**
340
         * Filters the link to the term archive page.
341
         *
342
         * @see   \Timber\Term::link()
343
         * @since 0.21.9
344
         *
345
         * @param string       $link The link.
346
         * @param \Timber\Term $term The term object.
347
         */
348
        $link = \apply_filters('timber/term/link', $link, $this);
3✔
349

350
        /**
351
         * Filters the link to the term archive page.
352
         *
353
         * @deprecated 0.21.9, use `timber/term/link`
354
         */
355
        $link = \apply_filters_deprecated(
3✔
356
            'timber_term_link',
3✔
357
            [$link, $this],
3✔
358
            '2.0.0',
3✔
359
            'timber/term/link'
3✔
360
        );
3✔
361

362
        return $link;
3✔
363
    }
364

365
    /**
366
     * Gets a term meta value.
367
     *
368
     * @api
369
     * @deprecated 2.0.0, use `{{ term.meta('field_name') }}` instead.
370
     * @see \Timber\Term::meta()
371
     *
372
     * @param string $field_name The field name for which you want to get the value.
373
     * @return mixed The meta field value.
374
     */
375
    public function get_field($field_name = null)
376
    {
377
        Helper::deprecated(
1✔
378
            "{{ term.get_field('field_name') }}",
1✔
379
            "{{ term.meta('field_name') }}",
1✔
380
            '2.0.0'
1✔
381
        );
1✔
382

383
        return $this->meta($field_name);
1✔
384
    }
385

386
    /**
387
     * Returns a relative link (path) to the term archive page like `/category/news`
388
     *
389
     * @api
390
     * @example
391
     * ```twig
392
     * See all posts in: <a href="{{ term.path }}">{{ term.name }}</a>
393
     * ```
394
     * @return string
395
     */
396
    public function path()
397
    {
398
        $link = $this->link();
2✔
399
        $rel = URLHelper::get_rel_url($link, true);
2✔
400

401
        /**
402
         * Filters the relative link (path) to a term archive page.
403
         *
404
         * @todo Add example
405
         *
406
         * @see   \Timber\Term::path()
407
         * @since 0.21.9
408
         *
409
         * @param string       $rel  The relative link.
410
         * @param \Timber\Term $term The term object.
411
         */
412
        $rel = \apply_filters('timber/term/path', $rel, $this);
2✔
413

414
        /**
415
         * Filters the relative link (path) to a term archive page.
416
         *
417
         * @deprecated 2.0.0, use `timber/term/path`
418
         */
419
        $rel = \apply_filters_deprecated(
2✔
420
            'timber_term_path',
2✔
421
            [$rel, $this],
2✔
422
            '2.0.0',
2✔
423
            'timber/term/path'
2✔
424
        );
2✔
425

426
        return $rel;
2✔
427
    }
428

429
    /**
430
     * Gets posts that have the current term assigned.
431
     *
432
     * @api
433
     * @example
434
     * Query the default posts_per_page for this Term:
435
     *
436
     * ```twig
437
     * <h4>Recent posts in {{ term.name }}</h4>
438
     *
439
     * <ul>
440
     * {% for post in term.posts() %}
441
     *     <li>
442
     *         <a href="{{ post.link }}">{{ post.title }}</a>
443
     *     </li>
444
     * {% endfor %}
445
     * </ul>
446
     * ```
447
     *
448
     * Query exactly 3 Posts from this Term:
449
     *
450
     * ```twig
451
     * <h4>Recent posts in {{ term.name }}</h4>
452
     *
453
     * <ul>
454
     * {% for post in term.posts(3) %}
455
     *     <li>
456
     *         <a href="{{ post.link }}">{{ post.title }}</a>
457
     *     </li>
458
     * {% endfor %}
459
     * </ul>
460
     * ```
461
     *
462
     * If you need more control over the query that is going to be performed, you can pass your
463
     * custom query arguments in the first parameter.
464
     *
465
     * ```twig
466
     * <h4>Our branches in {{ region.name }}</h4>
467
     *
468
     * <ul>
469
     * {% for branch in region.posts({
470
     *     post_type: 'branch',
471
     *     posts_per_page: -1,
472
     *     orderby: 'menu_order'
473
     * }) %}
474
     *     <li>
475
     *         <a href="{{ branch.link }}">{{ branch.title }}</a>
476
     *     </li>
477
     * {% endfor %}
478
     * </ul>
479
     * ```
480
     *
481
     * @param int|array $query           Optional. Either the number of posts or an array of
482
     *                                   arguments for the post query to be performed.
483
     *                                   Default is an empty array, the equivalent of:
484
     *                                   ```php
485
     *                                   [
486
     *                                     'posts_per_page' => get_option('posts_per_page'),
487
     *                                     'post_type'      => 'any',
488
     *                                     'tax_query'      => [ ...tax query for this Term... ]
489
     *                                   ]
490
     *                                   ```
491
     * @param string $post_type_or_class Deprecated. Before Timber 2.x this was a post_type to be
492
     *                                   used for querying posts OR the Timber\Post subclass to
493
     *                                   instantiate for each post returned. As of Timber 2.0.0,
494
     *                                   specify `post_type` in the `$query` array argument. To
495
     *                                   specify the class, use Class Maps.
496
     * @see https://timber.github.io/docs/v2/guides/posts/
497
     * @see https://timber.github.io/docs/v2/guides/class-maps/
498
     * @return \Timber\PostQuery
499
     */
500
    public function posts($query = [], $post_type_or_class = null)
501
    {
502
        if (\is_string($query)) {
10✔
503
            Helper::doing_it_wrong(
1✔
504
                'Passing a query string to Term::posts()',
1✔
505
                'Pass a query array instead: e.g. `"posts_per_page=3"` should be replaced with `["posts_per_page" => 3]`',
1✔
506
                '2.0.0'
1✔
507
            );
1✔
508

509
            return false;
1✔
510
        }
511

512
        if (\is_int($query)) {
9✔
513
            $query = [
2✔
514
                'posts_per_page' => $query,
2✔
515
                'post_type' => 'any',
2✔
516
            ];
2✔
517
        }
518

519
        if (isset($post_type_or_class)) {
9✔
520
            Helper::deprecated(
1✔
521
                'Passing post_type_or_class',
1✔
522
                'Pass post_type as part of the $query argument. For specifying class, use Class Maps: https://timber.github.io/docs/v2/guides/class-maps/',
1✔
523
                '2.0.0'
1✔
524
            );
1✔
525

526
            // Honor the non-deprecated posts_per_page param over the deprecated second arg.
527
            $query['post_type'] = $query['post_type'] ?? $post_type_or_class;
1✔
528
        }
529

530
        if (\func_num_args() > 2) {
9✔
531
            Helper::doing_it_wrong(
1✔
532
                'Passing a post class',
1✔
533
                'Use Class Maps instead: https://timber.github.io/docs/v2/guides/class-maps/',
1✔
534
                '2.0.0'
1✔
535
            );
1✔
536
        }
537

538
        $tax_query = [
9✔
539
            // Force a tax_query constraint on this term.
540
            'relation' => 'AND',
9✔
541
            [
9✔
542
                'field' => 'id',
9✔
543
                'terms' => $this->ID,
9✔
544
                'taxonomy' => $this->taxonomy,
9✔
545
            ],
9✔
546
        ];
9✔
547

548
        // Merge a clause for this Term into any user-specified tax_query clauses.
549
        $query['tax_query'] = \array_merge($query['tax_query'] ?? [], $tax_query);
9✔
550

551
        return Timber::get_posts($query);
9✔
552
    }
553

554
    /**
555
     * @api
556
     * @return string
557
     */
558
    public function title()
559
    {
560
        return $this->name;
18✔
561
    }
562

563
    /** DEPRECATED DOWN HERE
564
     * ======================
565
     **/
566

567
    /**
568
     * Get Posts that have been "tagged" with the particular term
569
     *
570
     * @api
571
     * @deprecated 2.0.0 use `{{ term.posts }}` instead
572
     *
573
     * @param int $numberposts
574
     * @return array|bool|null
575
     */
576
    public function get_posts($numberposts = 10)
577
    {
578
        Helper::deprecated('{{ term.get_posts }}', '{{ term.posts }}', '2.0.0');
1✔
579
        return $this->posts($numberposts);
1✔
580
    }
581

582
    /**
583
     * @api
584
     * @deprecated 2.0.0, use `{{ term.children }}` instead.
585
     *
586
     * @return array
587
     */
588
    public function get_children()
589
    {
590
        Helper::deprecated('{{ term.get_children }}', '{{ term.children }}', '2.0.0');
×
591

592
        return $this->children();
×
593
    }
594

595
    /**
596
     * Updates term_meta of the current object with the given value.
597
     *
598
     * @deprecated 2.0.0 Use `update_term_meta()` instead.
599
     *
600
     * @param string $key   The key of the meta field to update.
601
     * @param mixed  $value The new value.
602
     */
603
    public function update($key, $value)
604
    {
605
        Helper::deprecated('Timber\Term::update()', 'update_term_meta()', '2.0.0');
×
606

607
        /**
608
         * Filters term meta value that is going to be updated.
609
         *
610
         * @deprecated 2.0.0 with no replacement
611
         */
612
        $value = \apply_filters_deprecated(
×
613
            'timber_term_set_meta',
×
614
            [$value, $key, $this->ID, $this],
×
615
            '2.0.0',
×
616
            false,
×
617
            'This filter will be removed in a future version of Timber. There is no replacement.'
×
618
        );
×
619

620
        /**
621
         * Filters term meta value that is going to be updated.
622
         *
623
         * This filter is used by the ACF Integration.
624
         *
625
         * @deprecated 2.0.0, with no replacement
626
         */
627
        $value = \apply_filters_deprecated(
×
628
            'timber/term/meta/set',
×
629
            [$value, $key, $this->ID, $this],
×
630
            '2.0.0',
×
631
            false,
×
632
            'This filter will be removed in a future version of Timber. There is no replacement.'
×
633
        );
×
634

635
        $this->$key = $value;
×
636
    }
637
}
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