• 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

85.0
/src/Comment.php
1
<?php
2

3
namespace Timber;
4

5
use WP_Comment;
6

7
/**
8
 * Class Comment
9
 *
10
 * The `Timber\Comment` class is used to view the output of comments. 99% of the time this will be
11
 * in the context of the comments on a post. However you can also fetch a comment directly using its
12
 * comment ID.
13
 *
14
 * @api
15
 * @example
16
 * ```php
17
 * $comment = Timber::get_comment( $comment_id );
18
 *
19
 * $context = [
20
 *     'comment_of_the_day' => $comment
21
 * ];
22
 *
23
 * Timber::render('index.twig', $context);
24
 * ```
25
 *
26
 * ```twig
27
 * <p class="comment">{{comment_of_the_day.content}}</p>
28
 * <p class="comment-attribution">- {{comment.author.name}}</p>
29
 * ```
30
 *
31
 * ```html
32
 * <p class="comment">But, O Sarah! If the dead can come back to this earth and flit unseen around those they loved, I shall always be near you; in the garish day and in the darkest night -- amidst your happiest scenes and gloomiest hours - always, always; and if there be a soft breeze upon your cheek, it shall be my breath; or the cool air fans your throbbing temple, it shall be my spirit passing by.</p>
33
 * <p class="comment-attribution">- Sullivan Ballou</p>
34
 * ```
35
 */
36
class Comment extends CoreEntity
37
{
38
    /**
39
     * The underlying WordPress Core object.
40
     *
41
     * @since 2.0.0
42
     *
43
     * @var WP_Comment|null
44
     */
45
    protected ?WP_Comment $wp_object;
46

47
    public $object_type = 'comment';
48

49
    public static $representation = 'comment';
50

51
    /**
52
     * @api
53
     * @var int
54
     */
55
    public $ID;
56

57
    /**
58
     * @api
59
     * @var int
60
     */
61
    public $id;
62

63
    /**
64
     * @var int
65
     */
66
    public $comment_approved;
67

68
    /**
69
     * @api
70
     * @var string
71
     */
72
    public $comment_author_email;
73

74
    /**
75
     * @api
76
     * @var string
77
     */
78
    public $comment_content;
79

80
    /**
81
     * @api
82
     * @var string
83
     */
84
    public $comment_date;
85

86
    /**
87
     * @api
88
     * @var int
89
     */
90
    public $comment_ID;
91

92
    /**
93
     * @var int
94
     */
95
    public $comment_parent;
96

97
    /**
98
     * @api
99
     * @var int
100
     */
101
    public $user_id;
102

103
    /**
104
     * @api
105
     * @var int
106
     */
107
    public $post_id;
108

109
    /**
110
     * @api
111
     * @var string
112
     */
113
    public $comment_author;
114

115
    public $_depth = 0;
116

117
    protected $children = [];
118

119
    /**
120
     * Construct a Timber\Comment. This is protected to prevent direct instantiation,
121
     * which is no longer supported. Use `Timber::get_comment()` instead.
122
     *
123
     * @internal
124
     */
125
    final protected function __construct()
126
    {
127
    }
68✔
128

129
    /**
130
     * Build a Timber\Comment. Do not call this directly. Use `Timber::get_comment()` instead.
131
     *
132
     * @internal
133
     * @param WP_Comment $wp_comment a native WP_Comment instance
134
     */
135
    public static function build(WP_Comment $wp_comment): self
136
    {
137
        $comment = new static();
68✔
138
        $comment->import($wp_comment);
68✔
139
        $comment->ID = $wp_comment->comment_ID;
68✔
140
        $comment->id = $wp_comment->comment_ID;
68✔
141
        $comment->wp_object = $wp_comment;
68✔
142

143
        return $comment;
68✔
144
    }
145

146
    /**
147
     * Gets the content.
148
     *
149
     * @api
150
     * @return string
151
     */
152
    public function __toString()
153
    {
154
        return $this->content();
1✔
155
    }
156

157
    /**
158
     * @internal
159
     * @param integer $cid
160
     */
161
    public function init($cid)
162
    {
163
        $comment_data = $cid;
×
164
        if (\is_integer($cid)) {
×
165
            $comment_data = \get_comment($cid);
×
166
        }
167
        $this->import($comment_data);
×
168
        $this->ID = $this->comment_ID;
×
169
        $this->id = $this->comment_ID;
×
170
    }
171

172
    /**
173
     * Gets the underlying WordPress Core object.
174
     *
175
     * @since 2.0.0
176
     *
177
     * @return WP_Comment|null
178
     */
179
    public function wp_object(): ?WP_Comment
180
    {
181
        return $this->wp_object;
1✔
182
    }
183

184
    /**
185
     * Gets the author.
186
     *
187
     * @api
188
     * @example
189
     * ```twig
190
     * <h3>Comments by...</h3>
191
     * <ol>
192
     * {% for comment in post.comments %}
193
     *     <li>{{comment.author.name}}, who is a {{comment.author.roles[0]}}</li>
194
     * {% endfor %}
195
     * </ol>
196
     * ```
197
     * ```html
198
     * <h3>Comments by...</h3>
199
     * <ol>
200
     *  <li>Jared Novack, who is a contributor</li>
201
     *  <li>Katie Ricci, who is a subscriber</li>
202
     *  <li>Rebecca Pearl, who is a author</li>
203
     * </ol>
204
     * ```
205
     * @return User
206
     */
207
    public function author()
208
    {
209
        if ($this->user_id) {
6✔
210
            return Timber::get_user($this->user_id);
4✔
211
        } else {
212
            // We can't (and shouldn't) construct a full-blown User object,
213
            // so just return a stdclass inst with a name
214
            return (object) [
2✔
215
                'name' => $this->comment_author ?: 'Anonymous',
2✔
216
            ];
2✔
217
        }
218
    }
219

220
    /**
221
     * Fetches the Gravatar.
222
     *
223
     * @api
224
     * @example
225
     * ```twig
226
     * <img src="{{comment.avatar(36,template_uri~"/img/dude.jpg")}}" alt="Image of {{comment.author.name}}" />
227
     * ```
228
     * ```html
229
     * <img src="http://gravatar.com/i/sfsfsdfasdfsfa.jpg" alt="Image of Katherine Rich" />
230
     * ```
231
     * @param int|mixed    $size     Size of avatar.
232
     * @param string       $default  Default avatar URL.
233
     * @return bool|mixed|string
234
     */
235
    public function avatar($size = 92, $default = '')
236
    {
237
        if (!\get_option('show_avatars')) {
6✔
238
            return false;
1✔
239
        }
240
        if (!\is_numeric($size)) {
5✔
241
            $size = '92';
1✔
242
        }
243

244
        $email = $this->avatar_email();
5✔
245

246
        $args = [
5✔
247
            'size' => $size,
5✔
248
            'default' => $default,
5✔
249
        ];
5✔
250
        $args = \apply_filters('pre_get_avatar_data', $args, $email);
5✔
251
        if (isset($args['url'])) {
5✔
252
            return $args['url'];
×
253
        }
254

255
        $email_hash = '';
5✔
256
        if (!empty($email)) {
5✔
257
            $email_hash = \md5(\strtolower(\trim($email)));
1✔
258
        }
259
        $host = $this->avatar_host($email_hash);
5✔
260
        $default = $this->avatar_default($default, $email, $size, $host);
5✔
261
        if (!empty($email)) {
5✔
262
            $avatar = $this->avatar_out($default, $host, $email_hash, $size);
1✔
263
        } else {
264
            $avatar = $default;
4✔
265
        }
266
        return $avatar;
5✔
267
    }
268

269
    /**
270
     * Gets the content.
271
     *
272
     * @api
273
     * @return string
274
     */
275
    public function content()
276
    {
277
        return \trim(\apply_filters('comment_text', $this->comment_content));
3✔
278
    }
279

280
    /**
281
     * Gets the comment children.
282
     *
283
     * @api
284
     * @return array Comments
285
     */
286
    public function children()
287
    {
288
        return $this->children;
15✔
289
    }
290

291
    /**
292
     * Adds a child.
293
     *
294
     * @api
295
     * @param \Timber\Comment $child_comment Comment child to add.
296
     * @return array Comment children.
297
     */
298
    public function add_child(Comment $child_comment)
299
    {
300
        return $this->children[] = $child_comment;
7✔
301
    }
302

303
    /**
304
     * Updates the comment depth.
305
     *
306
     * @api
307
     * @param int $depth Level of depth.
308
     */
309
    public function update_depth($depth = 0)
310
    {
311
        $this->_depth = $depth;
15✔
312
        $children = $this->children();
15✔
313
        foreach ($children as $comment) {
15✔
314
            $child_depth = $depth + 1;
7✔
315
            $comment->update_depth($child_depth);
7✔
316
        }
317
    }
318

319
    /**
320
     * At what depth is this comment?
321
     *
322
     * @api
323
     * @return int
324
     */
325
    public function depth()
326
    {
327
        return $this->_depth;
3✔
328
    }
329

330
    /**
331
     * Is the comment approved?
332
     *
333
     * @api
334
     * @example
335
     * ```twig
336
     * {% if comment.approved %}
337
     *   Your comment is good
338
     * {% else %}
339
     *   Do you kiss your mother with that mouth?
340
     * {% endif %}
341
     * ```
342
     * @return boolean
343
     */
344
    public function approved()
345
    {
346
        return Helper::is_true($this->comment_approved);
1✔
347
    }
348

349
    /**
350
     * The date for the comment.
351
     *
352
     * @api
353
     * @example
354
     * ```twig
355
     * {% for comment in post.comments %}
356
     * <article class="comment">
357
     *   <p class="date">Posted on {{ comment.date }}:</p>
358
     *   <p class="comment">{{ comment.content }}</p>
359
     * </article>
360
     * {% endfor %}
361
     * ```
362
     * ```html
363
     * <article class="comment">
364
     *   <p class="date">Posted on September 28, 2015:</p>
365
     *   <p class="comment">Happy Birthday!</p>
366
     * </article>
367
     * ```
368
     * @param string $date_format of desired PHP date format (eg "M j, Y").
369
     * @return string
370
     */
371
    public function date($date_format = '')
372
    {
373
        $df = $date_format ? $date_format : \get_option('date_format');
1✔
374
        $the_date = (string) \mysql2date($df, $this->comment_date);
1✔
375
        return \apply_filters('get_comment_date ', $the_date, $df);
1✔
376
    }
377

378
    /**
379
     * What time was the comment posted?
380
     *
381
     * @api
382
     * @example
383
     * ```twig
384
     * {% for comment in post.comments %}
385
     * <article class="comment">
386
     *   <p class="date">Posted on {{ comment.date }} at {{comment.time}}:</p>
387
     *   <p class="comment">{{ comment.content }}</p>
388
     * </article>
389
     * {% endfor %}
390
     * ```
391
     * ```html
392
     * <article class="comment">
393
     *   <p class="date">Posted on September 28, 2015 at 12:45 am:</p>
394
     *   <p class="comment">Happy Birthday!</p>
395
     * </article>
396
     * ```
397
     * @param string $time_format of desired PHP time format (eg "H:i:s").
398
     * @return string
399
     */
400
    public function time($time_format = '')
401
    {
402
        $tf = $time_format ? $time_format : \get_option('time_format');
1✔
403
        $the_time = (string) \mysql2date($tf, $this->comment_date);
1✔
404
        return \apply_filters('get_comment_time', $the_time, $tf);
1✔
405
    }
406

407
    /**
408
     * Gets a comment meta value.
409
     *
410
     * @api
411
     * @deprecated 2.0.0, use `{{ comment.meta('field_name') }}` instead.
412
     *
413
     * @param string $field_name The field name for which you want to get the value.
414
     * @return mixed The meta field value.
415
     */
416
    public function get_meta_field($field_name)
417
    {
418
        Helper::deprecated(
×
419
            "{{ comment.get_meta_field('field_name') }}",
×
420
            "{{ comment.meta('field_name') }}",
×
421
            '2.0.0'
×
422
        );
×
423

424
        return $this->meta($field_name);
×
425
    }
426

427
    /**
428
     * Checks if the comment is a child.
429
     *
430
     * @api
431
     * @return bool
432
     */
433
    public function is_child()
434
    {
435
        return $this->comment_parent > 0;
15✔
436
    }
437

438
    /**
439
     * Gets a comment meta value.
440
     *
441
     * @api
442
     * @deprecated 2.0.0, use `{{ comment.meta('field_name') }}` instead.
443
     * @see \Timber\Comment::meta()
444
     *
445
     * @param string $field_name The field name for which you want to get the value.
446
     * @return mixed The meta field value.
447
     */
448
    public function get_field($field_name = null)
449
    {
450
        Helper::deprecated(
1✔
451
            "{{ comment.get_field('field_name') }}",
1✔
452
            "{{ comment.meta('field_name') }}",
1✔
453
            '2.0.0'
1✔
454
        );
1✔
455

456
        return $this->meta($field_name);
1✔
457
    }
458

459
    /**
460
     * Enqueue the WP threaded comments JavaScript, and fetch the reply link for various comments.
461
     *
462
     * @api
463
     * @param string $reply_text Text of the reply link.
464
     * @return string
465
     */
466
    public function reply_link($reply_text = 'Reply')
467
    {
468
        if (\is_singular() && \comments_open() && \get_option('thread_comments')) {
1✔
469
            \wp_enqueue_script('comment-reply');
×
470
        }
471

472
        // Get the comments depth option from the admin panel
473
        $max_depth = \get_option('thread_comments_depth');
1✔
474

475
        // Default args
476
        $args = [
1✔
477
            'add_below' => 'comment',
1✔
478
            'respond_id' => 'respond',
1✔
479
            'reply_text' => $reply_text,
1✔
480
            'depth' => $this->depth() + 1,
1✔
481
            'max_depth' => $max_depth,
1✔
482
        ];
1✔
483

484
        return \get_comment_reply_link($args, $this->ID, $this->post_id);
1✔
485
    }
486

487
    /**
488
     * Checks whether the current user can edit the comment.
489
     *
490
     * @api
491
     * @example
492
     * ```twig
493
     * {% if comment.can_edit %}
494
     *     <a href="{{ comment.edit_link }}">Edit</a>
495
     * {% endif %}
496
     * ```
497
     * @return bool
498
     */
499
    public function can_edit(): bool
500
    {
501
        return \current_user_can('edit_comment', $this->ID);
2✔
502
    }
503

504
    /**
505
     * Gets the edit link for a comment if the current user has the correct rights.
506
     *
507
     * @api
508
     * @since 2.0.0
509
     * @example
510
     * ```twig
511
     * {% if comment.can_edit %}
512
     *     <a href="{{ comment.edit_link }}">Edit</a>
513
     * {% endif %}
514
     * ```
515
     * @return string|null The edit URL of a comment in the WordPress admin or null if the current user can’t edit the
516
     *                     comment.
517
     */
518
    public function edit_link(): ?string
519
    {
520
        if (!$this->can_edit()) {
1✔
521
            return null;
1✔
522
        }
523

524
        return \get_edit_comment_link($this->ID);
1✔
525
    }
526

527
    /* AVATAR Stuff
528
    ======================= */
529

530
    /**
531
     * @internal
532
     * @return string
533
     */
534
    protected function avatar_email()
535
    {
536
        $id = (int) $this->user_id;
5✔
537
        $user = \get_userdata($id);
5✔
538
        if ($user) {
5✔
539
            $email = $user->user_email;
×
540
        } else {
541
            $email = $this->comment_author_email;
5✔
542
        }
543
        return $email;
5✔
544
    }
545

546
    /**
547
     * @internal
548
     * @param string $email_hash
549
     * @return string
550
     */
551
    protected function avatar_host($email_hash)
552
    {
553
        if (\is_ssl()) {
5✔
554
            $host = 'https://secure.gravatar.com';
1✔
555
        } else {
556
            if (!empty($email_hash)) {
5✔
557
                $host = \sprintf("http://%d.gravatar.com", (\hexdec($email_hash[0]) % 2));
1✔
558
            } else {
559
                $host = 'http://0.gravatar.com';
4✔
560
            }
561
        }
562
        return $host;
5✔
563
    }
564

565
    /**
566
     * @internal
567
     * @param string $default
568
     * @param string $email
569
     * @param string $size
570
     * @param string $host
571
     * @return string
572
     */
573
    protected function avatar_default($default, $email, $size, $host)
574
    {
575
        if (\substr($default, 0, 1) == '/') {
5✔
576
            $default = \home_url() . $default;
1✔
577
        }
578

579
        if (empty($default)) {
5✔
580
            $avatar_default = \get_option('avatar_default');
2✔
581
            if (empty($avatar_default)) {
2✔
582
                $default = 'mystery';
×
583
            } else {
584
                $default = $avatar_default;
2✔
585
            }
586
        }
587
        if ('mystery' == $default) {
5✔
588
            $default = $host . '/avatar/ad516503a11cd5ca435acc9bb6523536?s=' . $size;
3✔
589
            // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com')
590
        } elseif ('blank' == $default) {
3✔
591
            $default = $email ? 'blank' : \includes_url('images/blank.gif');
1✔
592
        } elseif (!empty($email) && 'gravatar_default' == $default) {
2✔
593
            $default = '';
×
594
        } elseif ('gravatar_default' == $default) {
2✔
595
            $default = $host . '/avatar/?s=' . $size;
1✔
596
        } elseif (empty($email) && !\strstr($default, 'http://')) {
1✔
597
            $default = $host . '/avatar/?d=' . $default . '&amp;s=' . $size;
×
598
        }
599
        return $default;
5✔
600
    }
601

602
    /**
603
     * @internal
604
     * @param string $default
605
     * @param string $host
606
     * @param string $email_hash
607
     * @param string $size
608
     * @return mixed
609
     */
610
    protected function avatar_out($default, $host, $email_hash, $size)
611
    {
612
        $out = $host . '/avatar/' . $email_hash . '?s=' . $size . '&amp;d=' . \urlencode($default);
1✔
613
        $rating = \get_option('avatar_rating');
1✔
614
        if (!empty($rating)) {
1✔
615
            $out .= '&amp;r=' . $rating;
1✔
616
        }
617
        return \str_replace('&#038;', '&amp;', \esc_url($out));
1✔
618
    }
619
}
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