1<?php
2/**
3 * Comment API: Walker_Comment class
4 *
5 * @package WordPress
6 * @subpackage Comments
7 * @since 4.4.0
8 */
9
10/**
11 * Core walker class used to create an HTML list of comments.
12 *
13 * @since 2.7.0
14 *
15 * @see Walker
16 */
17class Walker_Comment extends Walker {
18
19	/**
20	 * What the class handles.
21	 *
22	 * @since 2.7.0
23	 * @var string
24	 *
25	 * @see Walker::$tree_type
26	 */
27	public $tree_type = 'comment';
28
29	/**
30	 * Database fields to use.
31	 *
32	 * @since 2.7.0
33	 * @var array
34	 *
35	 * @see Walker::$db_fields
36	 * @todo Decouple this
37	 */
38	public $db_fields = array(
39		'parent' => 'comment_parent',
40		'id'     => 'comment_ID',
41	);
42
43	/**
44	 * Starts the list before the elements are added.
45	 *
46	 * @since 2.7.0
47	 *
48	 * @see Walker::start_lvl()
49	 * @global int $comment_depth
50	 *
51	 * @param string $output Used to append additional content (passed by reference).
52	 * @param int    $depth  Optional. Depth of the current comment. Default 0.
53	 * @param array  $args   Optional. Uses 'style' argument for type of HTML list. Default empty array.
54	 */
55	public function start_lvl( &$output, $depth = 0, $args = array() ) {
56		$GLOBALS['comment_depth'] = $depth + 1;
57
58		switch ( $args['style'] ) {
59			case 'div':
60				break;
61			case 'ol':
62				$output .= '<ol class="children">' . "\n";
63				break;
64			case 'ul':
65			default:
66				$output .= '<ul class="children">' . "\n";
67				break;
68		}
69	}
70
71	/**
72	 * Ends the list of items after the elements are added.
73	 *
74	 * @since 2.7.0
75	 *
76	 * @see Walker::end_lvl()
77	 * @global int $comment_depth
78	 *
79	 * @param string $output Used to append additional content (passed by reference).
80	 * @param int    $depth  Optional. Depth of the current comment. Default 0.
81	 * @param array  $args   Optional. Will only append content if style argument value is 'ol' or 'ul'.
82	 *                       Default empty array.
83	 */
84	public function end_lvl( &$output, $depth = 0, $args = array() ) {
85		$GLOBALS['comment_depth'] = $depth + 1;
86
87		switch ( $args['style'] ) {
88			case 'div':
89				break;
90			case 'ol':
91				$output .= "</ol><!-- .children -->\n";
92				break;
93			case 'ul':
94			default:
95				$output .= "</ul><!-- .children -->\n";
96				break;
97		}
98	}
99
100	/**
101	 * Traverses elements to create list from elements.
102	 *
103	 * This function is designed to enhance Walker::display_element() to
104	 * display children of higher nesting levels than selected inline on
105	 * the highest depth level displayed. This prevents them being orphaned
106	 * at the end of the comment list.
107	 *
108	 * Example: max_depth = 2, with 5 levels of nested content.
109	 *     1
110	 *      1.1
111	 *        1.1.1
112	 *        1.1.1.1
113	 *        1.1.1.1.1
114	 *        1.1.2
115	 *        1.1.2.1
116	 *     2
117	 *      2.2
118	 *
119	 * @since 2.7.0
120	 *
121	 * @see Walker::display_element()
122	 * @see wp_list_comments()
123	 *
124	 * @param WP_Comment $element           Comment data object.
125	 * @param array      $children_elements List of elements to continue traversing. Passed by reference.
126	 * @param int        $max_depth         Max depth to traverse.
127	 * @param int        $depth             Depth of the current element.
128	 * @param array      $args              An array of arguments.
129	 * @param string     $output            Used to append additional content. Passed by reference.
130	 */
131	public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
132		if ( ! $element ) {
133			return;
134		}
135
136		$id_field = $this->db_fields['id'];
137		$id       = $element->$id_field;
138
139		parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
140
141		/*
142		 * If at the max depth, and the current element still has children, loop over those
143		 * and display them at this level. This is to prevent them being orphaned to the end
144		 * of the list.
145		 */
146		if ( $max_depth <= $depth + 1 && isset( $children_elements[ $id ] ) ) {
147			foreach ( $children_elements[ $id ] as $child ) {
148				$this->display_element( $child, $children_elements, $max_depth, $depth, $args, $output );
149			}
150
151			unset( $children_elements[ $id ] );
152		}
153
154	}
155
156	/**
157	 * Starts the element output.
158	 *
159	 * @since 2.7.0
160	 *
161	 * @see Walker::start_el()
162	 * @see wp_list_comments()
163	 * @global int        $comment_depth
164	 * @global WP_Comment $comment       Global comment object.
165	 *
166	 * @param string     $output  Used to append additional content. Passed by reference.
167	 * @param WP_Comment $comment Comment data object.
168	 * @param int        $depth   Optional. Depth of the current comment in reference to parents. Default 0.
169	 * @param array      $args    Optional. An array of arguments. Default empty array.
170	 * @param int        $id      Optional. ID of the current comment. Default 0 (unused).
171	 */
172	public function start_el( &$output, $comment, $depth = 0, $args = array(), $id = 0 ) {
173		$depth++;
174		$GLOBALS['comment_depth'] = $depth;
175		$GLOBALS['comment']       = $comment;
176
177		if ( ! empty( $args['callback'] ) ) {
178			ob_start();
179			call_user_func( $args['callback'], $comment, $args, $depth );
180			$output .= ob_get_clean();
181			return;
182		}
183
184		if ( 'comment' === $comment->comment_type ) {
185			add_filter( 'comment_text', array( $this, 'filter_comment_text' ), 40, 2 );
186		}
187
188		if ( ( 'pingback' === $comment->comment_type || 'trackback' === $comment->comment_type ) && $args['short_ping'] ) {
189			ob_start();
190			$this->ping( $comment, $depth, $args );
191			$output .= ob_get_clean();
192		} elseif ( 'html5' === $args['format'] ) {
193			ob_start();
194			$this->html5_comment( $comment, $depth, $args );
195			$output .= ob_get_clean();
196		} else {
197			ob_start();
198			$this->comment( $comment, $depth, $args );
199			$output .= ob_get_clean();
200		}
201
202		if ( 'comment' === $comment->comment_type ) {
203			remove_filter( 'comment_text', array( $this, 'filter_comment_text' ), 40 );
204		}
205	}
206
207	/**
208	 * Ends the element output, if needed.
209	 *
210	 * @since 2.7.0
211	 *
212	 * @see Walker::end_el()
213	 * @see wp_list_comments()
214	 *
215	 * @param string     $output  Used to append additional content. Passed by reference.
216	 * @param WP_Comment $comment The current comment object. Default current comment.
217	 * @param int        $depth   Optional. Depth of the current comment. Default 0.
218	 * @param array      $args    Optional. An array of arguments. Default empty array.
219	 */
220	public function end_el( &$output, $comment, $depth = 0, $args = array() ) {
221		if ( ! empty( $args['end-callback'] ) ) {
222			ob_start();
223			call_user_func( $args['end-callback'], $comment, $args, $depth );
224			$output .= ob_get_clean();
225			return;
226		}
227		if ( 'div' === $args['style'] ) {
228			$output .= "</div><!-- #comment-## -->\n";
229		} else {
230			$output .= "</li><!-- #comment-## -->\n";
231		}
232	}
233
234	/**
235	 * Outputs a pingback comment.
236	 *
237	 * @since 3.6.0
238	 *
239	 * @see wp_list_comments()
240	 *
241	 * @param WP_Comment $comment The comment object.
242	 * @param int        $depth   Depth of the current comment.
243	 * @param array      $args    An array of arguments.
244	 */
245	protected function ping( $comment, $depth, $args ) {
246		$tag = ( 'div' === $args['style'] ) ? 'div' : 'li';
247		?>
248		<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( '', $comment ); ?>>
249			<div class="comment-body">
250				<?php _e( 'Pingback:' ); ?> <?php comment_author_link( $comment ); ?> <?php edit_comment_link( __( 'Edit' ), '<span class="edit-link">', '</span>' ); ?>
251			</div>
252		<?php
253	}
254
255	/**
256	 * Filters the comment text.
257	 *
258	 * Removes links from the pending comment's text if the commenter did not consent
259	 * to the comment cookies.
260	 *
261	 * @since 5.4.2
262	 *
263	 * @param string          $comment_text Text of the current comment.
264	 * @param WP_Comment|null $comment      The comment object. Null if not found.
265	 * @return string Filtered text of the current comment.
266	 */
267	public function filter_comment_text( $comment_text, $comment ) {
268		$commenter          = wp_get_current_commenter();
269		$show_pending_links = ! empty( $commenter['comment_author'] );
270
271		if ( $comment && '0' == $comment->comment_approved && ! $show_pending_links ) {
272			$comment_text = wp_kses( $comment_text, array() );
273		}
274
275		return $comment_text;
276	}
277
278	/**
279	 * Outputs a single comment.
280	 *
281	 * @since 3.6.0
282	 *
283	 * @see wp_list_comments()
284	 *
285	 * @param WP_Comment $comment Comment to display.
286	 * @param int        $depth   Depth of the current comment.
287	 * @param array      $args    An array of arguments.
288	 */
289	protected function comment( $comment, $depth, $args ) {
290		if ( 'div' === $args['style'] ) {
291			$tag       = 'div';
292			$add_below = 'comment';
293		} else {
294			$tag       = 'li';
295			$add_below = 'div-comment';
296		}
297
298		$commenter          = wp_get_current_commenter();
299		$show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author'];
300
301		if ( $commenter['comment_author_email'] ) {
302			$moderation_note = __( 'Your comment is awaiting moderation.' );
303		} else {
304			$moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' );
305		}
306		?>
307		<<?php echo $tag; ?> <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?> id="comment-<?php comment_ID(); ?>">
308		<?php if ( 'div' !== $args['style'] ) : ?>
309		<div id="div-comment-<?php comment_ID(); ?>" class="comment-body">
310		<?php endif; ?>
311		<div class="comment-author vcard">
312			<?php
313			if ( 0 != $args['avatar_size'] ) {
314				echo get_avatar( $comment, $args['avatar_size'] );
315			}
316			?>
317			<?php
318			$comment_author = get_comment_author_link( $comment );
319
320			if ( '0' == $comment->comment_approved && ! $show_pending_links ) {
321				$comment_author = get_comment_author( $comment );
322			}
323
324			printf(
325				/* translators: %s: Comment author link. */
326				__( '%s <span class="says">says:</span>' ),
327				sprintf( '<cite class="fn">%s</cite>', $comment_author )
328			);
329			?>
330		</div>
331		<?php if ( '0' == $comment->comment_approved ) : ?>
332		<em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em>
333		<br />
334		<?php endif; ?>
335
336		<div class="comment-meta commentmetadata">
337			<?php
338			printf(
339				'<a href="%s">%s</a>',
340				esc_url( get_comment_link( $comment, $args ) ),
341				sprintf(
342					/* translators: 1: Comment date, 2: Comment time. */
343					__( '%1$s at %2$s' ),
344					get_comment_date( '', $comment ),
345					get_comment_time()
346				)
347			);
348
349			edit_comment_link( __( '(Edit)' ), ' &nbsp;&nbsp;', '' );
350			?>
351		</div>
352
353		<?php
354		comment_text(
355			$comment,
356			array_merge(
357				$args,
358				array(
359					'add_below' => $add_below,
360					'depth'     => $depth,
361					'max_depth' => $args['max_depth'],
362				)
363			)
364		);
365		?>
366
367		<?php
368		comment_reply_link(
369			array_merge(
370				$args,
371				array(
372					'add_below' => $add_below,
373					'depth'     => $depth,
374					'max_depth' => $args['max_depth'],
375					'before'    => '<div class="reply">',
376					'after'     => '</div>',
377				)
378			)
379		);
380		?>
381
382		<?php if ( 'div' !== $args['style'] ) : ?>
383		</div>
384		<?php endif; ?>
385		<?php
386	}
387
388	/**
389	 * Outputs a comment in the HTML5 format.
390	 *
391	 * @since 3.6.0
392	 *
393	 * @see wp_list_comments()
394	 *
395	 * @param WP_Comment $comment Comment to display.
396	 * @param int        $depth   Depth of the current comment.
397	 * @param array      $args    An array of arguments.
398	 */
399	protected function html5_comment( $comment, $depth, $args ) {
400		$tag = ( 'div' === $args['style'] ) ? 'div' : 'li';
401
402		$commenter          = wp_get_current_commenter();
403		$show_pending_links = ! empty( $commenter['comment_author'] );
404
405		if ( $commenter['comment_author_email'] ) {
406			$moderation_note = __( 'Your comment is awaiting moderation.' );
407		} else {
408			$moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' );
409		}
410		?>
411		<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?>>
412			<article id="div-comment-<?php comment_ID(); ?>" class="comment-body">
413				<footer class="comment-meta">
414					<div class="comment-author vcard">
415						<?php
416						if ( 0 != $args['avatar_size'] ) {
417							echo get_avatar( $comment, $args['avatar_size'] );
418						}
419						?>
420						<?php
421						$comment_author = get_comment_author_link( $comment );
422
423						if ( '0' == $comment->comment_approved && ! $show_pending_links ) {
424							$comment_author = get_comment_author( $comment );
425						}
426
427						printf(
428							/* translators: %s: Comment author link. */
429							__( '%s <span class="says">says:</span>' ),
430							sprintf( '<b class="fn">%s</b>', $comment_author )
431						);
432						?>
433					</div><!-- .comment-author -->
434
435					<div class="comment-metadata">
436						<?php
437						printf(
438							'<a href="%s"><time datetime="%s">%s</time></a>',
439							esc_url( get_comment_link( $comment, $args ) ),
440							get_comment_time( 'c' ),
441							sprintf(
442								/* translators: 1: Comment date, 2: Comment time. */
443								__( '%1$s at %2$s' ),
444								get_comment_date( '', $comment ),
445								get_comment_time()
446							)
447						);
448
449						edit_comment_link( __( 'Edit' ), ' <span class="edit-link">', '</span>' );
450						?>
451					</div><!-- .comment-metadata -->
452
453					<?php if ( '0' == $comment->comment_approved ) : ?>
454					<em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em>
455					<?php endif; ?>
456				</footer><!-- .comment-meta -->
457
458				<div class="comment-content">
459					<?php comment_text(); ?>
460				</div><!-- .comment-content -->
461
462				<?php
463				if ( '1' == $comment->comment_approved || $show_pending_links ) {
464					comment_reply_link(
465						array_merge(
466							$args,
467							array(
468								'add_below' => 'div-comment',
469								'depth'     => $depth,
470								'max_depth' => $args['max_depth'],
471								'before'    => '<div class="reply">',
472								'after'     => '</div>',
473							)
474						)
475					);
476				}
477				?>
478			</article><!-- .comment-body -->
479		<?php
480	}
481}
482