113.90KiB; PHP | 2020-03-23 19:12:58+01 | SLOC 3199
1
<?php
2
/**
3
 * Core Comment API
4
 *
5
 * @package WordPress
6
 * @subpackage Comment
7
 */
8
9
/**
10
 * Check whether a comment passes internal checks to be allowed to add.
11
 *
12
 * If manual comment moderation is set in the administration, then all checks,
13
 * regardless of their type and whitelist, will fail and the function will
14
 * return false.
15
 *
16
 * If the number of links exceeds the amount in the administration, then the
17
 * check fails. If any of the parameter contents match the blacklist of words,
18
 * then the check fails.
19
 *
20
 * If the comment author was approved before, then the comment is automatically
21
 * whitelisted.
22
 *
23
 * If all checks pass, the function will return true.
24
 *
25
 * @since 1.2.0
26
 *
27
 * @global wpdb $wpdb WordPress database abstraction object.
28
 *
29
 * @param string $author       Comment author name.
30
 * @param string $email        Comment author email.
31
 * @param string $url          Comment author URL.
32
 * @param string $comment      Content of the comment.
33
 * @param string $user_ip      Comment author IP address.
34
 * @param string $user_agent   Comment author User-Agent.
35
 * @param string $comment_type Comment type, either user-submitted comment,
36
 *                             trackback, or pingback.
37
 * @return bool If all checks pass, true, otherwise false.
38
 */
39 16
function check_comment( $author, $email, $url, $comment, $user_ip, $user_agent, $comment_type ) {
40
	global $wpdb;
41
42
	// If manual moderation is enabled, skip all checks and return false.
43
	if ( 1 == get_option( 'comment_moderation' ) ) {
44
		return false;
45
	}
46
47
	/** This filter is documented in wp-includes/comment-template.php */
48
	$comment = apply_filters( 'comment_text', $comment, null, array() );
49
50
	// Check for the number of external links if a max allowed number is set.
51
	$max_links = get_option( 'comment_max_links' );
52
	if ( $max_links ) {
53 1
		$num_links = preg_match_all( '/<a [^>]*href/i', $comment, $out );
54
55
		/**
56
		 * Filters the number of links found in a comment.
57
		 *
58
		 * @since 3.0.0
59
		 * @since 4.7.0 Added the `$comment` parameter.
60
		 *
61
		 * @param int    $num_links The number of links found.
62
		 * @param string $url       Comment author's URL. Included in allowed links total.
63
		 * @param string $comment   Content of the comment.
64
		 */
65
		$num_links = apply_filters( 'comment_max_links_url', $num_links, $url, $comment );
66
67
		/*
68
		 * If the number of links in the comment exceeds the allowed amount,
69
		 * fail the check by returning false.
70
		 */
71
		if ( $num_links >= $max_links ) {
72
			return false;
73
		}
74
	}
75
76
	$mod_keys = trim( get_option( 'moderation_keys' ) );
77
78
	// If moderation 'keys' (keywords) are set, process them.
79
	if ( ! empty( $mod_keys ) ) {
80
		$words = explode( "\n", $mod_keys );
81
82
		foreach ( (array) $words as $word ) {
83
			$word = trim( $word );
84
85
			// Skip empty lines.
86
			if ( empty( $word ) ) {
87
				continue;
88
			}
89
90
			/*
91
			 * Do some escaping magic so that '#' (number of) characters in the spam
92
			 * words don't break things:
93
			 */
94
			$word = preg_quote( $word, '#' );
95
96
			/*
97
			 * Check the comment fields for moderation keywords. If any are found,
98
			 * fail the check for the given field by returning false.
99
			 */
100
			$pattern = "#$word#i";
101
			if ( preg_match( $pattern, $author ) ) {
102
				return false;
103
			}
104
			if ( preg_match( $pattern, $email ) ) {
105
				return false;
106
			}
107
			if ( preg_match( $pattern, $url ) ) {
108
				return false;
109
			}
110
			if ( preg_match( $pattern, $comment ) ) {
111
				return false;
112
			}
113
			if ( preg_match( $pattern, $user_ip ) ) {
114
				return false;
115
			}
116
			if ( preg_match( $pattern, $user_agent ) ) {
117
				return false;
118
			}
119
		}
120
	}
121
122
	/*
123
	 * Check if the option to approve comments by previously-approved authors is enabled.
124
	 *
125
	 * If it is enabled, check whether the comment author has a previously-approved comment,
126
	 * as well as whether there are any moderation keywords (if set) present in the author
127
	 * email address. If both checks pass, return true. Otherwise, return false.
128
	 */
129
	if ( 1 == get_option( 'comment_whitelist' ) ) {
130
		if ( 'trackback' != $comment_type && 'pingback' != $comment_type && $author != '' && $email != '' ) {
131
			$comment_user = get_user_by( 'email', wp_unslash( $email ) );
132
			if ( ! empty( $comment_user->ID ) ) {
133
				$ok_to_comment = $wpdb->get_var( $wpdb->prepare( "SELECT comment_approved FROM $wpdb->comments WHERE user_id = %d AND comment_approved = '1' LIMIT 1", $comment_user->ID ) );
134 1
			} else {
135
				// expected_slashed ($author, $email)
136
				$ok_to_comment = $wpdb->get_var( $wpdb->prepare( "SELECT comment_approved FROM $wpdb->comments WHERE comment_author = %s AND comment_author_email = %s and comment_approved = '1' LIMIT 1", $author, $email ) );
137
			}
138
			if ( ( 1 == $ok_to_comment ) &&
139
				( empty( $mod_keys ) || false === strpos( $email, $mod_keys ) ) ) {
140
					return true;
141 1
			} else {
142
				return false;
143
			}
144 1
		} else {
145
			return false;
146
		}
147
	}
148
	return true;
149
}
150
151
/**
152
 * Retrieve the approved comments for post $post_id.
153
 *
154
 * @since 2.0.0
155
 * @since 4.1.0 Refactored to leverage WP_Comment_Query over a direct query.
156
 *
157
 * @param  int   $post_id The ID of the post.
158
 * @param  array $args    Optional. See WP_Comment_Query::__construct() for information on accepted arguments.
159
 * @return int|array $comments The approved comments, or number of comments if `$count`
160
 *                             argument is true.
161
 */
162 1
function get_approved_comments( $post_id, $args = array() ) {
163
	if ( ! $post_id ) {
164
		return array();
165
	}
166
167
	$defaults    = array(
168
		'status'  => 1,
169
		'post_id' => $post_id,
170
		'order'   => 'ASC',
171
	);
172
	$parsed_args = wp_parse_args( $args, $defaults );
173
174
	$query = new WP_Comment_Query;
175
	return $query->query( $parsed_args );
176
}
177
178
/**
179
 * Retrieves comment data given a comment ID or comment object.
180
 *
181
 * If an object is passed then the comment data will be cached and then returned
182
 * after being passed through a filter. If the comment is empty, then the global
183
 * comment variable will be used, if it is set.
184
 *
185
 * @since 2.0.0
186
 *
187
 * @global WP_Comment $comment Global comment object.
188
 *
189
 * @param WP_Comment|string|int $comment Comment to retrieve.
190
 * @param string                $output  Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
191
 *                                       a WP_Comment object, an associative array, or a numeric array, respectively. Default OBJECT.
192
 * @return WP_Comment|array|null Depends on $output value.
193
 */
194 2
function get_comment( &$comment = null, $output = OBJECT ) {
195
	if ( empty( $comment ) && isset( $GLOBALS['comment'] ) ) {
196
		$comment = $GLOBALS['comment'];
197
	}
198
199
	if ( $comment instanceof WP_Comment ) {
200
		$_comment = $comment;
201
	} elseif ( is_object( $comment ) ) {
202
		$_comment = new WP_Comment( $comment );
203 1
	} else {
204 1
		$_comment = WP_Comment::get_instance( $comment );
205
	}
206
207
	if ( ! $_comment ) {
208
		return null;
209
	}
210
211
	/**
212
	 * Fires after a comment is retrieved.
213
	 *
214
	 * @since 2.3.0
215
	 *
216
	 * @param mixed $_comment Comment data.
217
	 */
218
	$_comment = apply_filters( 'get_comment', $_comment );
219
220
	if ( $output == OBJECT ) {
221
		return $_comment;
222
	} elseif ( $output == ARRAY_A ) {
223
		return $_comment->to_array();
224
	} elseif ( $output == ARRAY_N ) {
225
		return array_values( $_comment->to_array() );
226
	}
227
	return $_comment;
228
}
229
230
/**
231
 * Retrieve a list of comments.
232
 *
233
 * The comment list can be for the blog as a whole or for an individual post.
234
 *
235
 * @since 2.7.0
236
 *
237
 * @param string|array $args Optional. Array or string of arguments. See WP_Comment_Query::__construct()
238
 *                           for information on accepted arguments. Default empty.
239
 * @return int|array List of comments or number of found comments if `$count` argument is true.
240
 */
241
function get_comments( $args = '' ) {
242
	$query = new WP_Comment_Query;
243
	return $query->query( $args );
244
}
245
246
/**
247
 * Retrieve all of the WordPress supported comment statuses.
248
 *
249
 * Comments have a limited set of valid status values, this provides the comment
250
 * status values and descriptions.
251
 *
252
 * @since 2.7.0
253
 *
254
 * @return array List of comment statuses.
255
 */
256
function get_comment_statuses() {
257
	$status = array(
258
		'hold'    => __( 'Unapproved' ),
259
		'approve' => _x( 'Approved', 'comment status' ),
260
		'spam'    => _x( 'Spam', 'comment status' ),
261
		'trash'   => _x( 'Trash', 'comment status' ),
262
	);
263
264
	return $status;
265
}
266
267
/**
268
 * Gets the default comment status for a post type.
269
 *
270
 * @since 4.3.0
271
 *
272
 * @param string $post_type    Optional. Post type. Default 'post'.
273
 * @param string $comment_type Optional. Comment type. Default 'comment'.
274
 * @return string Expected return value is 'open' or 'closed'.
275
 */
276 2
function get_default_comment_status( $post_type = 'post', $comment_type = 'comment' ) {
277
	switch ( $comment_type ) {
278
		case 'pingback':
279
		case 'trackback':
280
			$supports = 'trackbacks';
281
			$option   = 'ping';
282
			break;
283
		default:
284
			$supports = 'comments';
285
			$option   = 'comment';
286
			break;
287
	}
288
289
	// Set the status.
290
	if ( 'page' === $post_type ) {
291
		$status = 'closed';
292
	} elseif ( post_type_supports( $post_type, $supports ) ) {
293
		$status = get_option( "default_{$option}_status" );
294 1
	} else {
295
		$status = 'closed';
296
	}
297
298
	/**
299
	 * Filters the default comment status for the given post type.
300
	 *
301
	 * @since 4.3.0
302
	 *
303
	 * @param string $status       Default status for the given post type,
304
	 *                             either 'open' or 'closed'.
305
	 * @param string $post_type    Post type. Default is `post`.
306
	 * @param string $comment_type Type of comment. Default is `comment`.
307
	 */
308
	return apply_filters( 'get_default_comment_status', $status, $post_type, $comment_type );
309
}
310
311
/**
312
 * The date the last comment was modified.
313
 *
314
 * @since 1.5.0
315
 * @since 4.7.0 Replaced caching the modified date in a local static variable
316
 *              with the Object Cache API.
317
 *
318
 * @global wpdb $wpdb WordPress database abstraction object.
319
 *
320
 * @param string $timezone Which timezone to use in reference to 'gmt', 'blog', or 'server' locations.
321
 * @return string|false Last comment modified date on success, false on failure.
322
 */
323
function get_lastcommentmodified( $timezone = 'server' ) {
324
	global $wpdb;
325
326
	$timezone = strtolower( $timezone );
327
	$key      = "lastcommentmodified:$timezone";
328
329 1
	$comment_modified_date = wp_cache_get( $key, 'timeinfo' );
330
	if ( false !== $comment_modified_date ) {
331
		return $comment_modified_date;
332
	}
333
334
	switch ( $timezone ) {
335
		case 'gmt':
336
			$comment_modified_date = $wpdb->get_var( "SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
337
			break;
338
		case 'blog':
339
			$comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
340
			break;
341
		case 'server':
342
			$add_seconds_server = gmdate( 'Z' );
343
344
			$comment_modified_date = $wpdb->get_var( $wpdb->prepare( "SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server ) );
345
			break;
346
	}
347
348
	if ( $comment_modified_date ) {
349
		wp_cache_set( $key, $comment_modified_date, 'timeinfo' );
350
351
		return $comment_modified_date;
352
	}
353
354
	return false;
355
}
356
357
/**
358
 * Retrieves the total comment counts for the whole site or a single post.
359
 *
360
 * Unlike wp_count_comments(), this function always returns the live comment counts without caching.
361
 *
362
 * @since 2.0.0
363
 *
364
 * @global wpdb $wpdb WordPress database abstraction object.
365
 *
366
 * @param int $post_id Optional. Restrict the comment counts to the given post. Default 0, which indicates that
367
 *                     comment counts for the whole site will be retrieved.
368
 * @return array() {
369
 *     The number of comments keyed by their status.
370
 *
371
 *     @type int|string $approved            The number of approved comments.
372
 *     @type int|string $awaiting_moderation The number of comments awaiting moderation (a.k.a. pending).
373
 *     @type int|string $spam                The number of spam comments.
374
 *     @type int|string $trash               The number of trashed comments.
375
 *     @type int|string $post-trashed        The number of comments for posts that are in the trash.
376
 *     @type int        $total_comments      The total number of non-trashed comments, including spam.
377
 *     @type int        $all                 The total number of pending or approved comments.
378
 * }
379
 */
380 1
function get_comment_count( $post_id = 0 ) {
381
	global $wpdb;
382
383
	$post_id = (int) $post_id;
384
385
	$where = '';
386
	if ( $post_id > 0 ) {
387
		$where = $wpdb->prepare( 'WHERE comment_post_ID = %d', $post_id );
388
	}
389
390
	$totals = (array) $wpdb->get_results(
391
		"
392
		SELECT comment_approved, COUNT( * ) AS total
393
		FROM {$wpdb->comments}
394
		{$where}
395
		GROUP BY comment_approved
396
	",
397
		ARRAY_A
398
	);
399
400
	$comment_count = array(
401
		'approved'            => 0,
402
		'awaiting_moderation' => 0,
403
		'spam'                => 0,
404
		'trash'               => 0,
405
		'post-trashed'        => 0,
406
		'total_comments'      => 0,
407
		'all'                 => 0,
408
	);
409
410
	foreach ( $totals as $row ) {
411
		switch ( $row['comment_approved'] ) {
412
			case 'trash':
413
				$comment_count['trash'] = $row['total'];
414
				break;
415
			case 'post-trashed':
416
				$comment_count['post-trashed'] = $row['total'];
417
				break;
418
			case 'spam':
419
				$comment_count['spam']            = $row['total'];
420
				$comment_count['total_comments'] += $row['total'];
421
				break;
422
			case '1':
423
				$comment_count['approved']        = $row['total'];
424
				$comment_count['total_comments'] += $row['total'];
425
				$comment_count['all']            += $row['total'];
426
				break;
427
			case '0':
428
				$comment_count['awaiting_moderation'] = $row['total'];
429
				$comment_count['total_comments']     += $row['total'];
430
				$comment_count['all']                += $row['total'];
431
				break;
432
			default:
433
				break;
434
		}
435
	}
436
437
	return $comment_count;
438
}
439
440
//
441
// Comment meta functions
442
//
443
444
/**
445
 * Add meta data field to a comment.
446
 *
447
 * @since 2.9.0
448
 * @link https://developer.wordpress.org/reference/functions/add_comment_meta/
449
 *
450
 * @param int $comment_id Comment ID.
451
 * @param string $meta_key Metadata name.
452
 * @param mixed $meta_value Metadata value.
453
 * @param bool $unique Optional, default is false. Whether the same key should not be added.
454
 * @return int|bool Meta ID on success, false on failure.
455
 */
456 4
function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false ) {
457
	return add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique );
458
}
459
460
/**
461
 * Remove metadata matching criteria from a comment.
462
 *
463
 * You can match based on the key, or key and value. Removing based on key and
464
 * value, will keep from removing duplicate metadata with the same key. It also
465
 * allows removing all metadata matching key, if needed.
466
 *
467
 * @since 2.9.0
468
 * @link https://developer.wordpress.org/reference/functions/delete_comment_meta/
469
 *
470
 * @param int $comment_id comment ID
471
 * @param string $meta_key Metadata name.
472
 * @param mixed $meta_value Optional. Metadata value.
473
 * @return bool True on success, false on failure.
474
 */
475
function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
476
	return delete_metadata( 'comment', $comment_id, $meta_key, $meta_value );
477
}
478
479
/**
480
 * Retrieve comment meta field for a comment.
481
 *
482
 * @since 2.9.0
483
 * @link https://developer.wordpress.org/reference/functions/get_comment_meta/
484
 *
485
 * @param int $comment_id Comment ID.
486
 * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys.
487
 * @param bool $single Whether to return a single value.
488
 * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
489
 *  is true.
490
 */
491 1
function get_comment_meta( $comment_id, $key = '', $single = false ) {
492
	return get_metadata( 'comment', $comment_id, $key, $single );
493
}
494
495
/**
496
 * Update comment meta field based on comment ID.
497
 *
498
 * Use the $prev_value parameter to differentiate between meta fields with the
499
 * same key and comment ID.
500
 *
501
 * If the meta field for the comment does not exist, it will be added.
502
 *
503
 * @since 2.9.0
504
 * @link https://developer.wordpress.org/reference/functions/update_comment_meta/
505
 *
506
 * @param int $comment_id Comment ID.
507
 * @param string $meta_key Metadata key.
508
 * @param mixed $meta_value Metadata value.
509
 * @param mixed $prev_value Optional. Previous value to check before removing.
510
 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
511
 */
512
function update_comment_meta( $comment_id, $meta_key, $meta_value, $prev_value = '' ) {
513
	return update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
514
}
515
516
/**
517
 * Queues comments for metadata lazy-loading.
518
 *
519
 * @since 4.5.0
520
 *
521
 * @param WP_Comment[] $comments Array of comment objects.
522
 */
523
function wp_queue_comments_for_comment_meta_lazyload( $comments ) {
524
	// Don't use `wp_list_pluck()` to avoid by-reference manipulation.
525
	$comment_ids = array();
526
	if ( is_array( $comments ) ) {
527
		foreach ( $comments as $comment ) {
528
			if ( $comment instanceof WP_Comment ) {
529
				$comment_ids[] = $comment->comment_ID;
530
			}
531
		}
532
	}
533
534
	if ( $comment_ids ) {
535
		$lazyloader = wp_metadata_lazyloader();
536
		$lazyloader->queue_objects( 'comment', $comment_ids );
537
	}
538
}
539
540
/**
541
 * Sets the cookies used to store an unauthenticated commentator's identity. Typically used
542
 * to recall previous comments by this commentator that are still held in moderation.
543
 *
544
 * @since 3.4.0
545
 * @since 4.9.6 The `$cookies_consent` parameter was added.
546
 *
547
 * @param WP_Comment $comment         Comment object.
548
 * @param WP_User    $user            Comment author's user object. The user may not exist.
549
 * @param boolean    $cookies_consent Optional. Comment author's consent to store cookies. Default true.
550
 */
551 1
function wp_set_comment_cookies( $comment, $user, $cookies_consent = true ) {
552
	// If the user already exists, or the user opted out of cookies, don't set cookies.
553
	if ( $user->exists() ) {
554
		return;
555
	}
556
557
	if ( false === $cookies_consent ) {
558
		// Remove any existing cookies.
559
		$past = time() - YEAR_IN_SECONDS;
560
		setcookie( 'comment_author_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
561
		setcookie( 'comment_author_email_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
562
		setcookie( 'comment_author_url_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
563
564
		return;
565
	}
566
567
	/**
568
	 * Filters the lifetime of the comment cookie in seconds.
569
	 *
570
	 * @since 2.8.0
571
	 *
572
	 * @param int $seconds Comment cookie lifetime. Default 30000000.
573
	 */
574 1
	$comment_cookie_lifetime = time() + apply_filters( 'comment_cookie_lifetime', 30000000 );
575
	$secure                  = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
576
	setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
577
	setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
578
	setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
579
}
580
581
/**
582
 * Sanitizes the cookies sent to the user already.
583
 *
584
 * Will only do anything if the cookies have already been created for the user.
585
 * Mostly used after cookies had been sent to use elsewhere.
586
 *
587
 * @since 2.0.4
588
 */
589 8
function sanitize_comment_cookies() {
590
	if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
591
		/**
592
		 * Filters the comment author's name cookie before it is set.
593
		 *
594
		 * When this filter hook is evaluated in wp_filter_comment(),
595
		 * the comment author's name string is passed.
596
		 *
597
		 * @since 1.5.0
598
		 *
599
		 * @param string $author_cookie The comment author name cookie.
600
		 */
601
		$comment_author                            = apply_filters( 'pre_comment_author_name', $_COOKIE[ 'comment_author_' . COOKIEHASH ] );
602
		$comment_author                            = wp_unslash( $comment_author );
603
		$comment_author                            = esc_attr( $comment_author );
604
		$_COOKIE[ 'comment_author_' . COOKIEHASH ] = $comment_author;
605
	}
606
607
	if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
608
		/**
609
		 * Filters the comment author's email cookie before it is set.
610
		 *
611
		 * When this filter hook is evaluated in wp_filter_comment(),
612
		 * the comment author's email string is passed.
613
		 *
614
		 * @since 1.5.0
615
		 *
616
		 * @param string $author_email_cookie The comment author email cookie.
617
		 */
618
		$comment_author_email                            = apply_filters( 'pre_comment_author_email', $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] );
619
		$comment_author_email                            = wp_unslash( $comment_author_email );
620
		$comment_author_email                            = esc_attr( $comment_author_email );
621
		$_COOKIE[ 'comment_author_email_' . COOKIEHASH ] = $comment_author_email;
622
	}
623
624
	if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
625
		/**
626
		 * Filters the comment author's URL cookie before it is set.
627
		 *
628
		 * When this filter hook is evaluated in wp_filter_comment(),
629
		 * the comment author's URL string is passed.
630
		 *
631
		 * @since 1.5.0
632
		 *
633
		 * @param string $author_url_cookie The comment author URL cookie.
634
		 */
635
		$comment_author_url                            = apply_filters( 'pre_comment_author_url', $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] );
636
		$comment_author_url                            = wp_unslash( $comment_author_url );
637
		$_COOKIE[ 'comment_author_url_' . COOKIEHASH ] = $comment_author_url;
638
	}
639
}
640
641
/**
642
 * Validates whether this comment is allowed to be made.
643
 *
644
 * @since 2.0.0
645
 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function to
646
 *              return a WP_Error object instead of dying.
647
 *
648
 * @global wpdb $wpdb WordPress database abstraction object.
649
 *
650
 * @param array $commentdata Contains information on the comment.
651
 * @param bool  $avoid_die   When true, a disallowed comment will result in the function
652
 *                           returning a WP_Error object, rather than executing wp_die().
653
 *                           Default false.
654
 * @return int|string|WP_Error Allowed comments return the approval status (0|1|'spam').
655
 *                             If `$avoid_die` is true, disallowed comments return a WP_Error.
656
 */
657 4
function wp_allow_comment( $commentdata, $avoid_die = false ) {
BooleanArgumentFlag The method wp_allow_comment has a boolean flag argument $avoid_die, which is a certain sign of a Single Responsibility Principle violation. (kritika/PHPMD) Filter like this
CyclomaticComplexity The function wp_allow_comment() has a Cyclomatic Complexity of 13. The configured cyclomatic complexity threshold is 10. (kritika/PHPMD) Filter like this
NPathComplexity The function wp_allow_comment() has an NPath complexity of 288. The configured NPath complexity threshold is 200. (kritika/PHPMD) Filter like this
ExcessiveMethodLength The function wp_allow_comment() has 172 lines of code. Current threshold is set to 100. Avoid really long methods. (kritika/PHPMD) Filter like this
658
	global $wpdb;
659
660
	// Simple duplicate check
661
	// expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
662
	$dupe = $wpdb->prepare(
663
		"SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = %s AND comment_approved != 'trash' AND ( comment_author = %s ",
664
		wp_unslash( $commentdata['comment_post_ID'] ),
665
		wp_unslash( $commentdata['comment_parent'] ),
666
		wp_unslash( $commentdata['comment_author'] )
667
	);
668
	if ( $commentdata['comment_author_email'] ) {
669
		$dupe .= $wpdb->prepare(
670
			'AND comment_author_email = %s ',
671
			wp_unslash( $commentdata['comment_author_email'] )
672
		);
673
	}
674
	$dupe .= $wpdb->prepare(
675
		') AND comment_content = %s LIMIT 1',
676
		wp_unslash( $commentdata['comment_content'] )
677
	);
678
679
	$dupe_id = $wpdb->get_var( $dupe );
680
681
	/**
682
	 * Filters the ID, if any, of the duplicate comment found when creating a new comment.
683
	 *
684
	 * Return an empty value from this filter to allow what WP considers a duplicate comment.
685
	 *
686
	 * @since 4.4.0
687
	 *
688
	 * @param int   $dupe_id     ID of the comment identified as a duplicate.
689
	 * @param array $commentdata Data for the comment being created.
690
	 */
691
	$dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata );
692
693
	if ( $dupe_id ) {
694
		/**
695
		 * Fires immediately after a duplicate comment is detected.
696
		 *
697
		 * @since 3.0.0
698
		 *
699
		 * @param array $commentdata Comment data.
700
		 */
701
		do_action( 'comment_duplicate_trigger', $commentdata );
702
703
		/**
704
		 * Filters duplicate comment error message.
705
		 *
706
		 * @since 5.2.0
707
		 *
708
		 * @param string $comment_duplicate_message Duplicate comment error message.
709
		 */
710 1
		$comment_duplicate_message = apply_filters( 'comment_duplicate_message', __( 'Duplicate comment detected; it looks as though you&#8217;ve already said that!' ) );
711
712
		if ( true === $avoid_die ) {
713
			return new WP_Error( 'comment_duplicate', $comment_duplicate_message, 409 );
714 1
		} else {
715
			if ( wp_doing_ajax() ) {
716 1
				die( $comment_duplicate_message );
717
			}
718
719
			wp_die( $comment_duplicate_message, 409 );
720
		}
721
	}
722
723
	/**
724
	 * Fires immediately before a comment is marked approved.
725
	 *
726
	 * Allows checking for comment flooding.
727
	 *
728
	 * @since 2.3.0
729
	 * @since 4.7.0 The `$avoid_die` parameter was added.
730
	 *
731
	 * @param string $comment_author_IP    Comment author's IP address.
732
	 * @param string $comment_author_email Comment author's email.
733
	 * @param string $comment_date_gmt     GMT date the comment was posted.
734
	 * @param bool   $avoid_die            Whether to prevent executing wp_die()
735
	 *                                     or die() if a comment flood is occurring.
736
	 */
737
	do_action(
738
		'check_comment_flood',
739
		$commentdata['comment_author_IP'],
740
		$commentdata['comment_author_email'],
741
		$commentdata['comment_date_gmt'],
742
		$avoid_die
743
	);
744
745
	/**
746
	 * Filters whether a comment is part of a comment flood.
747
	 *
748
	 * The default check is wp_check_comment_flood(). See check_comment_flood_db().
749
	 *
750
	 * @since 4.7.0
751
	 *
752
	 * @param bool   $is_flood             Is a comment flooding occurring? Default false.
753
	 * @param string $comment_author_IP    Comment author's IP address.
754
	 * @param string $comment_author_email Comment author's email.
755
	 * @param string $comment_date_gmt     GMT date the comment was posted.
756
	 * @param bool   $avoid_die            Whether to prevent executing wp_die()
757
	 *                                     or die() if a comment flood is occurring.
758
	 */
759
	$is_flood = apply_filters(
760
		'wp_is_comment_flood',
761
		false,
762
		$commentdata['comment_author_IP'],
763
		$commentdata['comment_author_email'],
764
		$commentdata['comment_date_gmt'],
765
		$avoid_die
766
	);
767
768
	if ( $is_flood ) {
769
		/** This filter is documented in wp-includes/comment-template.php */
770 1
		$comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );
771
772
		return new WP_Error( 'comment_flood', $comment_flood_message, 429 );
773
	}
774
775
	if ( ! empty( $commentdata['user_id'] ) ) {
776
		$user        = get_userdata( $commentdata['user_id'] );
777
		$post_author = $wpdb->get_var(
778
			$wpdb->prepare(
779
				"SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
780
				$commentdata['comment_post_ID']
781
			)
782
		);
783
	}
784
785
	if ( isset( $user ) && ( $commentdata['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
786
		// The author and the admins get respect.
787
		$approved = 1;
788 1
	} else {
789
		// Everyone else's comments will be checked.
790
		if ( check_comment(
791
			$commentdata['comment_author'],
792
			$commentdata['comment_author_email'],
793
			$commentdata['comment_author_url'],
794
			$commentdata['comment_content'],
795
			$commentdata['comment_author_IP'],
796
			$commentdata['comment_agent'],
797
			$commentdata['comment_type']
798
		) ) {
799
			$approved = 1;
800 1
		} else {
801
			$approved = 0;
802
		}
803
804
		if ( wp_blacklist_check(
805
			$commentdata['comment_author'],
806
			$commentdata['comment_author_email'],
807
			$commentdata['comment_author_url'],
808
			$commentdata['comment_content'],
809
			$commentdata['comment_author_IP'],
810
			$commentdata['comment_agent']
811
		) ) {
812
			$approved = EMPTY_TRASH_DAYS ? 'trash' : 'spam';
813
		}
814
	}
815
816
	/**
817
	 * Filters a comment's approval status before it is set.
818
	 *
819
	 * @since 2.1.0
820
	 * @since 4.9.0 Returning a WP_Error value from the filter will shortcircuit comment insertion and
821
	 *              allow skipping further processing.
822
	 *
823
	 * @param int|string|WP_Error $approved    The approval status. Accepts 1, 0, 'spam' or WP_Error.
824
	 * @param array               $commentdata Comment data.
825
	 */
826
	$approved = apply_filters( 'pre_comment_approved', $approved, $commentdata );
827
	return $approved;
828
}
829
830
/**
831
 * Hooks WP's native database-based comment-flood check.
832
 *
833
 * This wrapper maintains backward compatibility with plugins that expect to
834
 * be able to unhook the legacy check_comment_flood_db() function from
835
 * 'check_comment_flood' using remove_action().
836
 *
837
 * @since 2.3.0
838
 * @since 4.7.0 Converted to be an add_filter() wrapper.
839
 */
840
function check_comment_flood_db() {
841
	add_filter( 'wp_is_comment_flood', 'wp_check_comment_flood', 10, 5 );
842
}
843
844
/**
845
 * Checks whether comment flooding is occurring.
846
 *
847
 * Won't run, if current user can manage options, so to not block
848
 * administrators.
849
 *
850
 * @since 4.7.0
851
 *
852
 * @global wpdb $wpdb WordPress database abstraction object.
853
 *
854
 * @param bool   $is_flood  Is a comment flooding occurring?
855
 * @param string $ip        Comment author's IP address.
856
 * @param string $email     Comment author's email address.
857
 * @param string $date      MySQL time string.
858
 * @param bool   $avoid_die When true, a disallowed comment will result in the function
859
 *                          returning a WP_Error object, rather than executing wp_die().
860
 *                          Default false.
861
 * @return bool Whether comment flooding is occurring.
862
 */
863 2
function wp_check_comment_flood( $is_flood, $ip, $email, $date, $avoid_die = false ) {
864
865
	global $wpdb;
866
867
	// Another callback has declared a flood. Trust it.
868
	if ( true === $is_flood ) {
869
		return $is_flood;
870
	}
871
872
	// don't throttle admins or moderators
873
	if ( current_user_can( 'manage_options' ) || current_user_can( 'moderate_comments' ) ) {
874
		return false;
875
	}
876
	$hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
877
878
	if ( is_user_logged_in() ) {
879
		$user         = get_current_user_id();
880
		$check_column = '`user_id`';
881 1
	} else {
882
		$user         = $ip;
883
		$check_column = '`comment_author_IP`';
884
	}
885
886
	$sql      = $wpdb->prepare(
887
		"SELECT `comment_date_gmt` FROM `$wpdb->comments` WHERE `comment_date_gmt` >= %s AND ( $check_column = %s OR `comment_author_email` = %s ) ORDER BY `comment_date_gmt` DESC LIMIT 1",
888
		$hour_ago,
889
		$user,
890
		$email
891
	);
892
	$lasttime = $wpdb->get_var( $sql );
893
	if ( $lasttime ) {
894
		$time_lastcomment = mysql2date( 'U', $lasttime, false );
895
		$time_newcomment  = mysql2date( 'U', $date, false );
896
		/**
897
		 * Filters the comment flood status.
898
		 *
899
		 * @since 2.1.0
900
		 *
901
		 * @param bool $bool             Whether a comment flood is occurring. Default false.
902
		 * @param int  $time_lastcomment Timestamp of when the last comment was posted.
903
		 * @param int  $time_newcomment  Timestamp of when the new comment was posted.
904
		 */
905
		$flood_die = apply_filters( 'comment_flood_filter', false, $time_lastcomment, $time_newcomment );
906
		if ( $flood_die ) {
907
			/**
908
			 * Fires before the comment flood message is triggered.
909
			 *
910
			 * @since 1.5.0
911
			 *
912
			 * @param int $time_lastcomment Timestamp of when the last comment was posted.
913
			 * @param int $time_newcomment  Timestamp of when the new comment was posted.
914
			 */
915
			do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
916
917
			if ( true === $avoid_die ) {
918
				return true;
919 1
			} else {
920
				/**
921
				 * Filters the comment flood error message.
922
				 *
923
				 * @since 5.2.0
924
				 *
925
				 * @param string $comment_flood_message Comment flood error message.
926
				 */
927 1
				$comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );
928
929
				if ( wp_doing_ajax() ) {
930 1
					die( $comment_flood_message );
931
				}
932
933
				wp_die( $comment_flood_message, 429 );
934
			}
935
		}
936
	}
937
938
	return false;
939
}
940
941
/**
942
 * Separates an array of comments into an array keyed by comment_type.
943
 *
944
 * @since 2.7.0
945
 *
946
 * @param WP_Comment[] $comments Array of comments
947
 * @return WP_Comment[] Array of comments keyed by comment_type.
948
 */
949
function separate_comments( &$comments ) {
950
	$comments_by_type = array(
951
		'comment'   => array(),
952
		'trackback' => array(),
953
		'pingback'  => array(),
954
		'pings'     => array(),
955
	);
956
	$count            = count( $comments );
957
	for ( $i = 0; $i < $count; $i++ ) {
958
		$type = $comments[ $i ]->comment_type;
959
		if ( empty( $type ) ) {
960
			$type = 'comment';
961
		}
962
		$comments_by_type[ $type ][] = &$comments[ $i ];
963
		if ( 'trackback' == $type || 'pingback' == $type ) {
964
			$comments_by_type['pings'][] = &$comments[ $i ];
965
		}
966
	}
967
968
	return $comments_by_type;
969
}
970
971
/**
972
 * Calculate the total number of comment pages.
973
 *
974
 * @since 2.7.0
975
 *
976
 * @uses Walker_Comment
977
 *
978
 * @global WP_Query $wp_query WordPress Query object.
979
 *
980
 * @param WP_Comment[] $comments Optional. Array of WP_Comment objects. Defaults to $wp_query->comments.
981
 * @param int          $per_page Optional. Comments per page.
982
 * @param bool         $threaded Optional. Control over flat or threaded comments.
983
 * @return int Number of comment pages.
984
 */
985 2
function get_comment_pages_count( $comments = null, $per_page = null, $threaded = null ) {
986
	global $wp_query;
987
988
	if ( null === $comments && null === $per_page && null === $threaded && ! empty( $wp_query->max_num_comment_pages ) ) {
989
		return $wp_query->max_num_comment_pages;
990
	}
991
992
	if ( ( ! $comments || ! is_array( $comments ) ) && ! empty( $wp_query->comments ) ) {
993
		$comments = $wp_query->comments;
994
	}
995
996
	if ( empty( $comments ) ) {
997
		return 0;
998
	}
999
1000
	if ( ! get_option( 'page_comments' ) ) {
1001
		return 1;
1002
	}
1003
1004
	if ( ! isset( $per_page ) ) {
1005
		$per_page = (int) get_query_var( 'comments_per_page' );
1006
	}
1007
	if ( 0 === $per_page ) {
1008
		$per_page = (int) get_option( 'comments_per_page' );
1009
	}
1010
	if ( 0 === $per_page ) {
1011
		return 1;
1012
	}
1013
1014
	if ( ! isset( $threaded ) ) {
1015
		$threaded = get_option( 'thread_comments' );
1016
	}
1017
1018
	if ( $threaded ) {
1019
		$walker = new Walker_Comment;
1020
		$count  = ceil( $walker->get_number_of_root_elements( $comments ) / $per_page );
1021
	} else {
1022
		$count = ceil( count( $comments ) / $per_page );
1023
	}
1024
1025
	return $count;
1026
}
1027
1028
/**
1029
 * Calculate what page number a comment will appear on for comment paging.
1030
 *
1031
 * @since 2.7.0
1032
 *
1033
 * @global wpdb $wpdb WordPress database abstraction object.
1034
 *
1035
 * @param int   $comment_ID Comment ID.
1036
 * @param array $args {
1037
 *      Array of optional arguments.
1038
 *      @type string     $type      Limit paginated comments to those matching a given type. Accepts 'comment',
1039
 *                                  'trackback', 'pingback', 'pings' (trackbacks and pingbacks), or 'all'.
1040
 *                                  Default is 'all'.
1041
 *      @type int        $per_page  Per-page count to use when calculating pagination. Defaults to the value of the
1042
 *                                  'comments_per_page' option.
1043
 *      @type int|string $max_depth If greater than 1, comment page will be determined for the top-level parent of
1044
 *                                  `$comment_ID`. Defaults to the value of the 'thread_comments_depth' option.
1045
 * } *
1046
 * @return int|null Comment page number or null on error.
1047
 */
1048 3
function get_page_of_comment( $comment_ID, $args = array() ) {
1049
	global $wpdb;
1050
1051
	$page = null;
1052
1053
	$comment = get_comment( $comment_ID );
1054
	if ( ! $comment ) {
1055
		return;
1056
	}
1057
1058
	$defaults      = array(
1059
		'type'      => 'all',
1060
		'page'      => '',
1061
		'per_page'  => '',
1062
		'max_depth' => '',
1063
	);
1064
	$args          = wp_parse_args( $args, $defaults );
1065
	$original_args = $args;
1066
1067
	// Order of precedence: 1. `$args['per_page']`, 2. 'comments_per_page' query_var, 3. 'comments_per_page' option.
1068
	if ( get_option( 'page_comments' ) ) {
1069
		if ( '' === $args['per_page'] ) {
1070
			$args['per_page'] = get_query_var( 'comments_per_page' );
1071
		}
1072
1073
		if ( '' === $args['per_page'] ) {
1074
			$args['per_page'] = get_option( 'comments_per_page' );
1075
		}
1076
	}
1077
1078
	if ( empty( $args['per_page'] ) ) {
1079
		$args['per_page'] = 0;
1080
		$args['page']     = 0;
1081
	}
1082
1083
	if ( $args['per_page'] < 1 ) {
1084
		$page = 1;
1085
	}
1086
1087
	if ( null === $page ) {
1088
		if ( '' === $args['max_depth'] ) {
1089
			if ( get_option( 'thread_comments' ) ) {
1090
				$args['max_depth'] = get_option( 'thread_comments_depth' );
1091
			} else {
1092
				$args['max_depth'] = -1;
1093
			}
1094
		}
1095
1096
		// Find this comment's top level parent if threading is enabled
1097
		if ( $args['max_depth'] > 1 && 0 != $comment->comment_parent ) {
1098
			return get_page_of_comment( $comment->comment_parent, $args );
1099
		}
1100
1101
		$comment_args = array(
1102
			'type'       => $args['type'],
1103
			'post_id'    => $comment->comment_post_ID,
1104
			'fields'     => 'ids',
1105
			'count'      => true,
1106
			'status'     => 'approve',
1107
			'parent'     => 0,
1108
			'date_query' => array(
1109
				array(
1110
					'column' => "$wpdb->comments.comment_date_gmt",
1111
					'before' => $comment->comment_date_gmt,
1112
				),
1113
			),
1114
		);
1115
1116
		$comment_query       = new WP_Comment_Query();
1117
		$older_comment_count = $comment_query->query( $comment_args );
1118
1119
		// No older comments? Then it's page #1.
1120
		if ( 0 == $older_comment_count ) {
1121
			$page = 1;
1122
1123
			// Divide comments older than this one by comments per page to get this comment's page number
1124
		} else {
1125
			$page = ceil( ( $older_comment_count + 1 ) / $args['per_page'] );
1126
		}
1127
	}
1128
1129
	/**
1130
	 * Filters the calculated page on which a comment appears.
1131
	 *
1132
	 * @since 4.4.0
1133
	 * @since 4.7.0 Introduced the `$comment_ID` parameter.
1134
	 *
1135
	 * @param int   $page          Comment page.
1136
	 * @param array $args {
1137
	 *     Arguments used to calculate pagination. These include arguments auto-detected by the function,
1138
	 *     based on query vars, system settings, etc. For pristine arguments passed to the function,
1139
	 *     see `$original_args`.
1140
	 *
1141
	 *     @type string $type      Type of comments to count.
1142
	 *     @type int    $page      Calculated current page.
1143
	 *     @type int    $per_page  Calculated number of comments per page.
1144
	 *     @type int    $max_depth Maximum comment threading depth allowed.
1145
	 * }
1146
	 * @param array $original_args {
1147
	 *     Array of arguments passed to the function. Some or all of these may not be set.
1148
	 *
1149
	 *     @type string $type      Type of comments to count.
1150
	 *     @type int    $page      Current comment page.
1151
	 *     @type int    $per_page  Number of comments per page.
1152
	 *     @type int    $max_depth Maximum comment threading depth allowed.
1153
	 * }
1154
	 * @param int $comment_ID ID of the comment.
1155
	 */
1156
	return apply_filters( 'get_page_of_comment', (int) $page, $args, $original_args, $comment_ID );
1157
}
1158
1159
/**
1160
 * Retrieves the maximum character lengths for the comment form fields.
1161
 *
1162
 * @since 4.5.0
1163
 *
1164
 * @global wpdb $wpdb WordPress database abstraction object.
1165
 *
1166
 * @return array Maximum character length for the comment form fields.
1167
 */
1168 1
function wp_get_comment_fields_max_lengths() {
1169
	global $wpdb;
1170
1171
	$lengths = array(
1172
		'comment_author'       => 245,
1173
		'comment_author_email' => 100,
1174
		'comment_author_url'   => 200,
1175
		'comment_content'      => 65525,
1176
	);
1177
1178
	if ( $wpdb->is_mysql ) {
1179 1
		foreach ( $lengths as $column => $length ) {
1180
			$col_length = $wpdb->get_col_length( $wpdb->comments, $column );
1181
			$max_length = 0;
1182
1183
			// No point if we can't get the DB column lengths
1184
			if ( is_wp_error( $col_length ) ) {
1185
				break;
1186
			}
1187
1188
			if ( ! is_array( $col_length ) && (int) $col_length > 0 ) {
1189
				$max_length = (int) $col_length;
1190
			} elseif ( is_array( $col_length ) && isset( $col_length['length'] ) && intval( $col_length['length'] ) > 0 ) {
1191
				$max_length = (int) $col_length['length'];
1192
1193
				if ( ! empty( $col_length['type'] ) && 'byte' === $col_length['type'] ) {
1194
					$max_length = $max_length - 10;
1195
				}
1196
			}
1197
1198
			if ( $max_length > 0 ) {
1199
				$lengths[ $column ] = $max_length;
1200
			}
1201
		}
1202
	}
1203
1204
	/**
1205
	 * Filters the lengths for the comment form fields.
1206
	 *
1207
	 * @since 4.5.0
1208
	 *
1209
	 * @param array $lengths Associative array `'field_name' => 'maximum length'`.
1210
	 */
1211
	return apply_filters( 'wp_get_comment_fields_max_lengths', $lengths );
1212
}
1213
1214
/**
1215
 * Compares the lengths of comment data against the maximum character limits.
1216
 *
1217
 * @since 4.7.0
1218
 *
1219
 * @param array $comment_data Array of arguments for inserting a comment.
1220
 * @return WP_Error|true WP_Error when a comment field exceeds the limit,
1221
 *                       otherwise true.
1222
 */
1223
function wp_check_comment_data_max_lengths( $comment_data ) {
1224
	$max_lengths = wp_get_comment_fields_max_lengths();
1225
1226
	if ( isset( $comment_data['comment_author'] ) && mb_strlen( $comment_data['comment_author'], '8bit' ) > $max_lengths['comment_author'] ) {
1227
		return new WP_Error( 'comment_author_column_length', __( '<strong>ERROR</strong>: your name is too long.' ), 200 );
1228
	}
1229
1230
	if ( isset( $comment_data['comment_author_email'] ) && strlen( $comment_data['comment_author_email'] ) > $max_lengths['comment_author_email'] ) {
1231
		return new WP_Error( 'comment_author_email_column_length', __( '<strong>ERROR</strong>: your email address is too long.' ), 200 );
1232
	}
1233
1234
	if ( isset( $comment_data['comment_author_url'] ) && strlen( $comment_data['comment_author_url'] ) > $max_lengths['comment_author_url'] ) {
1235
		return new WP_Error( 'comment_author_url_column_length', __( '<strong>ERROR</strong>: your url is too long.' ), 200 );
1236
	}
1237
1238
	if ( isset( $comment_data['comment_content'] ) && mb_strlen( $comment_data['comment_content'], '8bit' ) > $max_lengths['comment_content'] ) {
1239
		return new WP_Error( 'comment_content_column_length', __( '<strong>ERROR</strong>: your comment is too long.' ), 200 );
1240
	}
1241
1242
	return true;
1243
}
1244
1245
/**
1246
 * Does comment contain blacklisted characters or words.
1247
 *
1248
 * @since 1.5.0
1249
 *
1250
 * @param string $author The author of the comment
1251
 * @param string $email The email of the comment
1252
 * @param string $url The url used in the comment
1253
 * @param string $comment The comment content
1254
 * @param string $user_ip The comment author's IP address
1255
 * @param string $user_agent The author's browser user agent
1256
 * @return bool True if comment contains blacklisted content, false if comment does not
1257
 */
1258 1
function wp_blacklist_check( $author, $email, $url, $comment, $user_ip, $user_agent ) {
1259
	/**
1260
	 * Fires before the comment is tested for blacklisted characters or words.
1261
	 *
1262
	 * @since 1.5.0
1263
	 *
1264
	 * @param string $author     Comment author.
1265
	 * @param string $email      Comment author's email.
1266
	 * @param string $url        Comment author's URL.
1267
	 * @param string $comment    Comment content.
1268
	 * @param string $user_ip    Comment author's IP address.
1269
	 * @param string $user_agent Comment author's browser user agent.
1270
	 */
1271
	do_action( 'wp_blacklist_check', $author, $email, $url, $comment, $user_ip, $user_agent );
1272
1273
	$mod_keys = trim( get_option( 'blacklist_keys' ) );
1274
	if ( '' == $mod_keys ) {
1275
		return false; // If moderation keys are empty
1276
	}
1277
1278
	// Ensure HTML tags are not being used to bypass the blacklist.
1279
	$comment_without_html = wp_strip_all_tags( $comment );
1280
1281
	$words = explode( "\n", $mod_keys );
1282
1283
	foreach ( (array) $words as $word ) {
1284
		$word = trim( $word );
1285
1286
		// Skip empty lines
1287
		if ( empty( $word ) ) {
1288
			continue; }
1289
1290
		// Do some escaping magic so that '#' chars in the
1291
		// spam words don't break things:
1292
		$word = preg_quote( $word, '#' );
1293
1294
		$pattern = "#$word#i";
1295
		if ( preg_match( $pattern, $author )
1296
			|| preg_match( $pattern, $email )
1297
			|| preg_match( $pattern, $url )
1298
			|| preg_match( $pattern, $comment )
1299
			|| preg_match( $pattern, $comment_without_html )
1300
			|| preg_match( $pattern, $user_ip )
1301
			|| preg_match( $pattern, $user_agent )
1302
		) {
1303
			return true;
1304
		}
1305
	}
1306
	return false;
1307
}
1308
1309
/**
1310
 * Retrieves the total comment counts for the whole site or a single post.
1311
 *
1312
 * The comment stats are cached and then retrieved, if they already exist in the
1313
 * cache.
1314
 *
1315
 * @see get_comment_count() Which handles fetching the live comment counts.
1316
 *
1317
 * @since 2.5.0
1318
 *
1319
 * @param int $post_id Optional. Restrict the comment counts to the given post. Default 0, which indicates that
1320
 *                     comment counts for the whole site will be retrieved.
1321
 * @return stdClass {
1322
 *     The number of comments keyed by their status.
1323
 *
1324
 *     @type int|string $approved       The number of approved comments.
1325
 *     @type int|string $moderated      The number of comments awaiting moderation (a.k.a. pending).
1326
 *     @type int|string $spam           The number of spam comments.
1327
 *     @type int|string $trash          The number of trashed comments.
1328
 *     @type int|string $post-trashed   The number of comments for posts that are in the trash.
1329
 *     @type int        $total_comments The total number of non-trashed comments, including spam.
1330
 *     @type int        $all            The total number of pending or approved comments.
1331
 * }
1332
 */
1333
function wp_count_comments( $post_id = 0 ) {
1334
	$post_id = (int) $post_id;
1335
1336
	/**
1337
	 * Filters the comments count for a given post or the whole site.
1338
	 *
1339
	 * @since 2.7.0
1340
	 *
1341
	 * @param array|stdClass $count   An empty array or an object containing comment counts.
1342
	 * @param int            $post_id The post ID. Can be 0 to represent the whole site.
1343
	 */
1344
	$filtered = apply_filters( 'wp_count_comments', array(), $post_id );
1345
	if ( ! empty( $filtered ) ) {
1346
		return $filtered;
1347
	}
1348
1349
	$count = wp_cache_get( "comments-{$post_id}", 'counts' );
1350
	if ( false !== $count ) {
1351
		return $count;
1352
	}
1353
1354
	$stats              = get_comment_count( $post_id );
1355
	$stats['moderated'] = $stats['awaiting_moderation'];
1356
	unset( $stats['awaiting_moderation'] );
1357
1358
	$stats_object = (object) $stats;
1359
	wp_cache_set( "comments-{$post_id}", $stats_object, 'counts' );
1360
1361
	return $stats_object;
1362
}
1363
1364
/**
1365
 * Trashes or deletes a comment.
1366
 *
1367
 * The comment is moved to trash instead of permanently deleted unless trash is
1368
 * disabled, item is already in the trash, or $force_delete is true.
1369
 *
1370
 * The post comment count will be updated if the comment was approved and has a
1371
 * post ID available.
1372
 *
1373
 * @since 2.0.0
1374
 *
1375
 * @global wpdb $wpdb WordPress database abstraction object.
1376
 *
1377
 * @param int|WP_Comment $comment_id   Comment ID or WP_Comment object.
1378
 * @param bool           $force_delete Whether to bypass trash and force deletion. Default is false.
1379
 * @return bool True on success, false on failure.
1380
 */
1381 2
function wp_delete_comment( $comment_id, $force_delete = false ) {
1382
	global $wpdb;
1383
	$comment = get_comment( $comment_id );
1384
	if ( ! $comment ) {
1385
		return false;
1386
	}
1387
1388
	if ( ! $force_delete && EMPTY_TRASH_DAYS && ! in_array( wp_get_comment_status( $comment ), array( 'trash', 'spam' ) ) ) {
1389
		return wp_trash_comment( $comment_id );
1390
	}
1391
1392
	/**
1393
	 * Fires immediately before a comment is deleted from the database.
1394
	 *
1395
	 * @since 1.2.0
1396
	 * @since 4.9.0 Added the `$comment` parameter.
1397
	 *
1398
	 * @param int        $comment_id The comment ID.
1399
	 * @param WP_Comment $comment    The comment to be deleted.
1400
	 */
1401
	do_action( 'delete_comment', $comment->comment_ID, $comment );
1402
1403
	// Move children up a level.
1404
	$children = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_parent = %d", $comment->comment_ID ) );
1405
	if ( ! empty( $children ) ) {
1406
		$wpdb->update( $wpdb->comments, array( 'comment_parent' => $comment->comment_parent ), array( 'comment_parent' => $comment->comment_ID ) );
1407
		clean_comment_cache( $children );
1408
	}
1409
1410
	// Delete metadata
1411
	$meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) );
1412
	foreach ( $meta_ids as $mid ) {
1413
		delete_metadata_by_mid( 'comment', $mid );
1414
	}
1415
1416
	if ( ! $wpdb->delete( $wpdb->comments, array( 'comment_ID' => $comment->comment_ID ) ) ) {
1417
		return false;
1418
	}
1419
1420
	/**
1421
	 * Fires immediately after a comment is deleted from the database.
1422
	 *
1423
	 * @since 2.9.0
1424
	 * @since 4.9.0 Added the `$comment` parameter.
1425
	 *
1426
	 * @param int        $comment_id The comment ID.
1427
	 * @param WP_Comment $comment    The deleted comment.
1428
	 */
1429
	do_action( 'deleted_comment', $comment->comment_ID, $comment );
1430
1431
	$post_id = $comment->comment_post_ID;
1432
	if ( $post_id && $comment->comment_approved == 1 ) {
1433
		wp_update_comment_count( $post_id );
1434
	}
1435
1436
	clean_comment_cache( $comment->comment_ID );
1437
1438
	/** This action is documented in wp-includes/comment.php */
1439
	do_action( 'wp_set_comment_status', $comment->comment_ID, 'delete' );
1440
1441
	wp_transition_comment_status( 'delete', $comment->comment_approved, $comment );
1442
	return true;
1443
}
1444
1445
/**
1446
 * Moves a comment to the Trash
1447
 *
1448
 * If trash is disabled, comment is permanently deleted.
1449
 *
1450
 * @since 2.9.0
1451
 *
1452
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1453
 * @return bool True on success, false on failure.
1454
 */
1455
function wp_trash_comment( $comment_id ) {
1456
	if ( ! EMPTY_TRASH_DAYS ) {
1457
		return wp_delete_comment( $comment_id, true );
1458
	}
1459
1460
	$comment = get_comment( $comment_id );
1461
	if ( ! $comment ) {
1462
		return false;
1463
	}
1464
1465
	/**
1466
	 * Fires immediately before a comment is sent to the Trash.
1467
	 *
1468
	 * @since 2.9.0
1469
	 * @since 4.9.0 Added the `$comment` parameter.
1470
	 *
1471
	 * @param int        $comment_id The comment ID.
1472
	 * @param WP_Comment $comment    The comment to be trashed.
1473
	 */
1474
	do_action( 'trash_comment', $comment->comment_ID, $comment );
1475
1476
	if ( wp_set_comment_status( $comment, 'trash' ) ) {
1477
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1478
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1479
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', $comment->comment_approved );
1480
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_time', time() );
1481
1482
		/**
1483
		 * Fires immediately after a comment is sent to Trash.
1484
		 *
1485
		 * @since 2.9.0
1486
		 * @since 4.9.0 Added the `$comment` parameter.
1487
		 *
1488
		 * @param int        $comment_id The comment ID.
1489
		 * @param WP_Comment $comment    The trashed comment.
1490
		 */
1491
		do_action( 'trashed_comment', $comment->comment_ID, $comment );
1492
		return true;
1493
	}
1494
1495
	return false;
1496
}
1497
1498
/**
1499
 * Removes a comment from the Trash
1500
 *
1501
 * @since 2.9.0
1502
 *
1503
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1504
 * @return bool True on success, false on failure.
1505
 */
1506
function wp_untrash_comment( $comment_id ) {
1507
	$comment = get_comment( $comment_id );
1508
	if ( ! $comment ) {
1509
		return false;
1510
	}
1511
1512
	/**
1513
	 * Fires immediately before a comment is restored from the Trash.
1514
	 *
1515
	 * @since 2.9.0
1516
	 * @since 4.9.0 Added the `$comment` parameter.
1517
	 *
1518
	 * @param int        $comment_id The comment ID.
1519
	 * @param WP_Comment $comment    The comment to be untrashed.
1520
	 */
1521
	do_action( 'untrash_comment', $comment->comment_ID, $comment );
1522
1523
	$status = (string) get_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', true );
1524
	if ( empty( $status ) ) {
1525
		$status = '0';
1526
	}
1527
1528
	if ( wp_set_comment_status( $comment, $status ) ) {
1529
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1530
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1531
		/**
1532
		 * Fires immediately after a comment is restored from the Trash.
1533
		 *
1534
		 * @since 2.9.0
1535
		 * @since 4.9.0 Added the `$comment` parameter.
1536
		 *
1537
		 * @param int        $comment_id The comment ID.
1538
		 * @param WP_Comment $comment    The untrashed comment.
1539
		 */
1540
		do_action( 'untrashed_comment', $comment->comment_ID, $comment );
1541
		return true;
1542
	}
1543
1544
	return false;
1545
}
1546
1547
/**
1548
 * Marks a comment as Spam
1549
 *
1550
 * @since 2.9.0
1551
 *
1552
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1553
 * @return bool True on success, false on failure.
1554
 */
1555
function wp_spam_comment( $comment_id ) {
1556
	$comment = get_comment( $comment_id );
1557
	if ( ! $comment ) {
1558
		return false;
1559
	}
1560
1561
	/**
1562
	 * Fires immediately before a comment is marked as Spam.
1563
	 *
1564
	 * @since 2.9.0
1565
	 * @since 4.9.0 Added the `$comment` parameter.
1566
	 *
1567
	 * @param int        $comment_id The comment ID.
1568
	 * @param WP_Comment $comment    The comment to be marked as spam.
1569
	 */
1570
	do_action( 'spam_comment', $comment->comment_ID, $comment );
1571
1572
	if ( wp_set_comment_status( $comment, 'spam' ) ) {
1573
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1574
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1575
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', $comment->comment_approved );
1576
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_time', time() );
1577
		/**
1578
		 * Fires immediately after a comment is marked as Spam.
1579
		 *
1580
		 * @since 2.9.0
1581
		 * @since 4.9.0 Added the `$comment` parameter.
1582
		 *
1583
		 * @param int        $comment_id The comment ID.
1584
		 * @param WP_Comment $comment    The comment marked as spam.
1585
		 */
1586
		do_action( 'spammed_comment', $comment->comment_ID, $comment );
1587
		return true;
1588
	}
1589
1590
	return false;
1591
}
1592
1593
/**
1594
 * Removes a comment from the Spam
1595
 *
1596
 * @since 2.9.0
1597
 *
1598
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1599
 * @return bool True on success, false on failure.
1600
 */
1601
function wp_unspam_comment( $comment_id ) {
1602
	$comment = get_comment( $comment_id );
1603
	if ( ! $comment ) {
1604
		return false;
1605
	}
1606
1607
	/**
1608
	 * Fires immediately before a comment is unmarked as Spam.
1609
	 *
1610
	 * @since 2.9.0
1611
	 * @since 4.9.0 Added the `$comment` parameter.
1612
	 *
1613
	 * @param int        $comment_id The comment ID.
1614
	 * @param WP_Comment $comment    The comment to be unmarked as spam.
1615
	 */
1616
	do_action( 'unspam_comment', $comment->comment_ID, $comment );
1617
1618
	$status = (string) get_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', true );
1619
	if ( empty( $status ) ) {
1620
		$status = '0';
1621
	}
1622
1623
	if ( wp_set_comment_status( $comment, $status ) ) {
1624
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1625
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1626
		/**
1627
		 * Fires immediately after a comment is unmarked as Spam.
1628
		 *
1629
		 * @since 2.9.0
1630
		 * @since 4.9.0 Added the `$comment` parameter.
1631
		 *
1632
		 * @param int        $comment_id The comment ID.
1633
		 * @param WP_Comment $comment    The comment unmarked as spam.
1634
		 */
1635
		do_action( 'unspammed_comment', $comment->comment_ID, $comment );
1636
		return true;
1637
	}
1638
1639
	return false;
1640
}
1641
1642
/**
1643
 * The status of a comment by ID.
1644
 *
1645
 * @since 1.0.0
1646
 *
1647
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object
1648
 * @return false|string Status might be 'trash', 'approved', 'unapproved', 'spam'. False on failure.
1649
 */
1650
function wp_get_comment_status( $comment_id ) {
1651
	$comment = get_comment( $comment_id );
1652
	if ( ! $comment ) {
1653
		return false;
1654
	}
1655
1656
	$approved = $comment->comment_approved;
1657
1658
	if ( $approved == null ) {
1659
		return false;
1660
	} elseif ( $approved == '1' ) {
1661
		return 'approved';
1662
	} elseif ( $approved == '0' ) {
1663
		return 'unapproved';
1664
	} elseif ( $approved == 'spam' ) {
1665
		return 'spam';
1666
	} elseif ( $approved == 'trash' ) {
1667
		return 'trash';
1668
	} else {
1669
		return false;
1670
	}
1671
}
1672
1673
/**
1674
 * Call hooks for when a comment status transition occurs.
1675
 *
1676
 * Calls hooks for comment status transitions. If the new comment status is not the same
1677
 * as the previous comment status, then two hooks will be ran, the first is
1678
 * {@see 'transition_comment_status'} with new status, old status, and comment data. The
1679
 * next action called is {@see comment_$old_status_to_$new_status'}. It has the
1680
 * comment data.
1681
 *
1682
 * The final action will run whether or not the comment statuses are the same. The
1683
 * action is named {@see 'comment_$new_status_$comment->comment_type'}.
1684
 *
1685
 * @since 2.7.0
1686
 *
1687
 * @param string $new_status New comment status.
1688
 * @param string $old_status Previous comment status.
1689
 * @param object $comment Comment data.
1690
 */
1691
function wp_transition_comment_status( $new_status, $old_status, $comment ) {
1692
	/*
1693
	 * Translate raw statuses to human readable formats for the hooks.
1694
	 * This is not a complete list of comment status, it's only the ones
1695
	 * that need to be renamed
1696
	 */
1697
	$comment_statuses = array(
1698
		0         => 'unapproved',
1699
		'hold'    => 'unapproved', // wp_set_comment_status() uses "hold"
1700
		1         => 'approved',
1701
		'approve' => 'approved', // wp_set_comment_status() uses "approve"
1702
	);
1703
	if ( isset( $comment_statuses[ $new_status ] ) ) {
1704
		$new_status = $comment_statuses[ $new_status ];
1705
	}
1706
	if ( isset( $comment_statuses[ $old_status ] ) ) {
1707
		$old_status = $comment_statuses[ $old_status ];
1708
	}
1709
1710
	// Call the hooks
1711
	if ( $new_status != $old_status ) {
1712
		/**
1713
		 * Fires when the comment status is in transition.
1714
		 *
1715
		 * @since 2.7.0
1716
		 *
1717
		 * @param int|string $new_status The new comment status.
1718
		 * @param int|string $old_status The old comment status.
1719
		 * @param object     $comment    The comment data.
1720
		 */
1721
		do_action( 'transition_comment_status', $new_status, $old_status, $comment );
1722
		/**
1723
		 * Fires when the comment status is in transition from one specific status to another.
1724
		 *
1725
		 * The dynamic portions of the hook name, `$old_status`, and `$new_status`,
1726
		 * refer to the old and new comment statuses, respectively.
1727
		 *
1728
		 * @since 2.7.0
1729
		 *
1730
		 * @param WP_Comment $comment Comment object.
1731
		 */
1732
		do_action( "comment_{$old_status}_to_{$new_status}", $comment );
1733
	}
1734
	/**
1735
	 * Fires when the status of a specific comment type is in transition.
1736
	 *
1737
	 * The dynamic portions of the hook name, `$new_status`, and `$comment->comment_type`,
1738
	 * refer to the new comment status, and the type of comment, respectively.
1739
	 *
1740
	 * Typical comment types include an empty string (standard comment), 'pingback',
1741
	 * or 'trackback'.
1742
	 *
1743
	 * @since 2.7.0
1744
	 *
1745
	 * @param int        $comment_ID The comment ID.
1746
	 * @param WP_Comment $comment    Comment object.
1747
	 */
1748
	do_action( "comment_{$new_status}_{$comment->comment_type}", $comment->comment_ID, $comment );
1749
}
1750
1751
/**
1752
 * Clear the lastcommentmodified cached value when a comment status is changed.
1753
 *
1754
 * Deletes the lastcommentmodified cache key when a comment enters or leaves
1755
 * 'approved' status.
1756
 *
1757
 * @since 4.7.0
1758
 * @access private
1759
 *
1760
 * @param string $new_status The new comment status.
1761
 * @param string $old_status The old comment status.
1762
 */
1763
function _clear_modified_cache_on_transition_comment_status( $new_status, $old_status ) {
1764
	if ( 'approved' === $new_status || 'approved' === $old_status ) {
1765
		foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
1766
			wp_cache_delete( "lastcommentmodified:$timezone", 'timeinfo' );
1767
		}
1768
	}
1769
}
1770
1771
/**
1772
 * Get current commenter's name, email, and URL.
1773
 *
1774
 * Expects cookies content to already be sanitized. User of this function might
1775
 * wish to recheck the returned array for validity.
1776
 *
1777
 * @see sanitize_comment_cookies() Use to sanitize cookies
1778
 *
1779
 * @since 2.0.4
1780
 *
1781
 * @return array Comment author, email, url respectively.
1782
 */
1783
function wp_get_current_commenter() {
1784
	// Cookies should already be sanitized.
1785
1786
	$comment_author = '';
1787
	if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
1788
		$comment_author = $_COOKIE[ 'comment_author_' . COOKIEHASH ];
1789
	}
1790
1791
	$comment_author_email = '';
1792
	if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
1793
		$comment_author_email = $_COOKIE[ 'comment_author_email_' . COOKIEHASH ];
1794
	}
1795
1796
	$comment_author_url = '';
1797
	if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
1798
		$comment_author_url = $_COOKIE[ 'comment_author_url_' . COOKIEHASH ];
1799
	}
1800
1801
	/**
1802
	 * Filters the current commenter's name, email, and URL.
1803
	 *
1804
	 * @since 3.1.0
1805
	 *
1806
	 * @param array $comment_author_data {
1807
	 *     An array of current commenter variables.
1808
	 *
1809
	 *     @type string $comment_author       The name of the author of the comment. Default empty.
1810
	 *     @type string $comment_author_email The email address of the `$comment_author`. Default empty.
1811
	 *     @type string $comment_author_url   The URL address of the `$comment_author`. Default empty.
1812
	 * }
1813
	 */
1814
	return apply_filters( 'wp_get_current_commenter', compact( 'comment_author', 'comment_author_email', 'comment_author_url' ) );
1815
}
1816
1817
/**
1818
 * Get unapproved comment author's email.
1819
 *
1820
 * Used to allow the commenter to see their pending comment.
1821
 *
1822
 * @since 5.1.0
1823
 *
1824
 * @return string The unapproved comment author's email (when supplied).
1825
 */
1826
function wp_get_unapproved_comment_author_email() {
1827
	$commenter_email = '';
1828
1829
	if ( ! empty( $_GET['unapproved'] ) && ! empty( $_GET['moderation-hash'] ) ) {
1830
		$comment_id = (int) $_GET['unapproved'];
1831
		$comment    = get_comment( $comment_id );
1832
1833
		if ( $comment && hash_equals( $_GET['moderation-hash'], wp_hash( $comment->comment_date_gmt ) ) ) {
1834
			$commenter_email = $comment->comment_author_email;
1835
		}
1836
	}
1837
1838
	if ( ! $commenter_email ) {
1839
		$commenter       = wp_get_current_commenter();
1840
		$commenter_email = $commenter['comment_author_email'];
1841
	}
1842
1843
	return $commenter_email;
1844
}
1845
1846
/**
1847
 * Inserts a comment into the database.
1848
 *
1849
 * @since 2.0.0
1850
 * @since 4.4.0 Introduced `$comment_meta` argument.
1851
 *
1852
 * @global wpdb $wpdb WordPress database abstraction object.
1853
 *
1854
 * @param array $commentdata {
1855
 *     Array of arguments for inserting a new comment.
1856
 *
1857
 *     @type string     $comment_agent        The HTTP user agent of the `$comment_author` when
1858
 *                                            the comment was submitted. Default empty.
1859
 *     @type int|string $comment_approved     Whether the comment has been approved. Default 1.
1860
 *     @type string     $comment_author       The name of the author of the comment. Default empty.
1861
 *     @type string     $comment_author_email The email address of the `$comment_author`. Default empty.
1862
 *     @type string     $comment_author_IP    The IP address of the `$comment_author`. Default empty.
1863
 *     @type string     $comment_author_url   The URL address of the `$comment_author`. Default empty.
1864
 *     @type string     $comment_content      The content of the comment. Default empty.
1865
 *     @type string     $comment_date         The date the comment was submitted. To set the date
1866
 *                                            manually, `$comment_date_gmt` must also be specified.
1867
 *                                            Default is the current time.
1868
 *     @type string     $comment_date_gmt     The date the comment was submitted in the GMT timezone.
1869
 *                                            Default is `$comment_date` in the site's GMT timezone.
1870
 *     @type int        $comment_karma        The karma of the comment. Default 0.
1871
 *     @type int        $comment_parent       ID of this comment's parent, if any. Default 0.
1872
 *     @type int        $comment_post_ID      ID of the post that relates to the comment, if any.
1873
 *                                            Default 0.
1874
 *     @type string     $comment_type         Comment type. Default empty.
1875
 *     @type array      $comment_meta         Optional. Array of key/value pairs to be stored in commentmeta for the
1876
 *                                            new comment.
1877
 *     @type int        $user_id              ID of the user who submitted the comment. Default 0.
1878
 * }
1879
 * @return int|false The new comment's ID on success, false on failure.
1880
 */
1881 2
function wp_insert_comment( $commentdata ) {
1882
	global $wpdb;
1883
	$data = wp_unslash( $commentdata );
1884
1885
	$comment_author       = ! isset( $data['comment_author'] ) ? '' : $data['comment_author'];
1886
	$comment_author_email = ! isset( $data['comment_author_email'] ) ? '' : $data['comment_author_email'];
1887
	$comment_author_url   = ! isset( $data['comment_author_url'] ) ? '' : $data['comment_author_url'];
1888
	$comment_author_IP    = ! isset( $data['comment_author_IP'] ) ? '' : $data['comment_author_IP'];
1889
1890
	$comment_date     = ! isset( $data['comment_date'] ) ? current_time( 'mysql' ) : $data['comment_date'];
1891
	$comment_date_gmt = ! isset( $data['comment_date_gmt'] ) ? get_gmt_from_date( $comment_date ) : $data['comment_date_gmt'];
1892
1893
	$comment_post_ID  = ! isset( $data['comment_post_ID'] ) ? 0 : $data['comment_post_ID'];
1894
	$comment_content  = ! isset( $data['comment_content'] ) ? '' : $data['comment_content'];
1895
	$comment_karma    = ! isset( $data['comment_karma'] ) ? 0 : $data['comment_karma'];
1896
	$comment_approved = ! isset( $data['comment_approved'] ) ? 1 : $data['comment_approved'];
1897
	$comment_agent    = ! isset( $data['comment_agent'] ) ? '' : $data['comment_agent'];
1898
	$comment_type     = ! isset( $data['comment_type'] ) ? '' : $data['comment_type'];
1899
	$comment_parent   = ! isset( $data['comment_parent'] ) ? 0 : $data['comment_parent'];
1900
1901
	$user_id = ! isset( $data['user_id'] ) ? 0 : $data['user_id'];
1902
1903
	$compacted = compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_date', 'comment_date_gmt', 'comment_content', 'comment_karma', 'comment_approved', 'comment_agent', 'comment_type', 'comment_parent', 'user_id' );
1904
	if ( ! $wpdb->insert( $wpdb->comments, $compacted ) ) {
1905
		return false;
1906
	}
1907
1908 1
	$id = (int) $wpdb->insert_id;
1909
1910
	if ( $comment_approved == 1 ) {
1911
		wp_update_comment_count( $comment_post_ID );
1912
1913
		foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
1914
			wp_cache_delete( "lastcommentmodified:$timezone", 'timeinfo' );
1915
		}
1916
	}
1917
1918
	clean_comment_cache( $id );
1919
1920
	$comment = get_comment( $id );
1921
1922
	// If metadata is provided, store it.
1923
	if ( isset( $commentdata['comment_meta'] ) && is_array( $commentdata['comment_meta'] ) ) {
1924
		foreach ( $commentdata['comment_meta'] as $meta_key => $meta_value ) {
1925
			add_comment_meta( $comment->comment_ID, $meta_key, $meta_value, true );
1926
		}
1927
	}
1928
1929
	/**
1930
	 * Fires immediately after a comment is inserted into the database.
1931
	 *
1932
	 * @since 2.8.0
1933
	 *
1934
	 * @param int        $id      The comment ID.
1935
	 * @param WP_Comment $comment Comment object.
1936
	 */
1937
	do_action( 'wp_insert_comment', $id, $comment );
1938
1939
	return $id;
1940
}
1941
1942
/**
1943
 * Filters and sanitizes comment data.
1944
 *
1945
 * Sets the comment data 'filtered' field to true when finished. This can be
1946
 * checked as to whether the comment should be filtered and to keep from
1947
 * filtering the same comment more than once.
1948
 *
1949
 * @since 2.0.0
1950
 *
1951
 * @param array $commentdata Contains information on the comment.
1952
 * @return array Parsed comment information.
1953
 */
1954
function wp_filter_comment( $commentdata ) {
1955
	if ( isset( $commentdata['user_ID'] ) ) {
1956
		/**
1957
		 * Filters the comment author's user id before it is set.
1958
		 *
1959
		 * The first time this filter is evaluated, 'user_ID' is checked
1960
		 * (for back-compat), followed by the standard 'user_id' value.
1961
		 *
1962
		 * @since 1.5.0
1963
		 *
1964
		 * @param int $user_ID The comment author's user ID.
1965
		 */
1966
		$commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_ID'] );
1967
	} elseif ( isset( $commentdata['user_id'] ) ) {
1968
		/** This filter is documented in wp-includes/comment.php */
1969
		$commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_id'] );
1970
	}
1971
1972
	/**
1973
	 * Filters the comment author's browser user agent before it is set.
1974
	 *
1975
	 * @since 1.5.0
1976
	 *
1977
	 * @param string $comment_agent The comment author's browser user agent.
1978
	 */
1979
	$commentdata['comment_agent'] = apply_filters( 'pre_comment_user_agent', ( isset( $commentdata['comment_agent'] ) ? $commentdata['comment_agent'] : '' ) );
1980
	/** This filter is documented in wp-includes/comment.php */
1981
	$commentdata['comment_author'] = apply_filters( 'pre_comment_author_name', $commentdata['comment_author'] );
1982
	/**
1983
	 * Filters the comment content before it is set.
1984
	 *
1985
	 * @since 1.5.0
1986
	 *
1987
	 * @param string $comment_content The comment content.
1988
	 */
1989
	$commentdata['comment_content'] = apply_filters( 'pre_comment_content', $commentdata['comment_content'] );
1990
	/**
1991
	 * Filters the comment author's IP address before it is set.
1992
	 *
1993
	 * @since 1.5.0
1994
	 *
1995
	 * @param string $comment_author_ip The comment author's IP address.
1996
	 */
1997
	$commentdata['comment_author_IP'] = apply_filters( 'pre_comment_user_ip', $commentdata['comment_author_IP'] );
1998
	/** This filter is documented in wp-includes/comment.php */
1999
	$commentdata['comment_author_url'] = apply_filters( 'pre_comment_author_url', $commentdata['comment_author_url'] );
2000
	/** This filter is documented in wp-includes/comment.php */
2001
	$commentdata['comment_author_email'] = apply_filters( 'pre_comment_author_email', $commentdata['comment_author_email'] );
2002
	$commentdata['filtered']             = true;
2003
	return $commentdata;
2004
}
2005
2006
/**
2007
 * Whether a comment should be blocked because of comment flood.
2008
 *
2009
 * @since 2.1.0
2010
 *
2011
 * @param bool $block Whether plugin has already blocked comment.
2012
 * @param int $time_lastcomment Timestamp for last comment.
2013
 * @param int $time_newcomment Timestamp for new comment.
2014
 * @return bool Whether comment should be blocked.
2015
 */
2016
function wp_throttle_comment_flood( $block, $time_lastcomment, $time_newcomment ) {
2017
	if ( $block ) { // a plugin has already blocked... we'll let that decision stand
2018
		return $block;
2019
	}
2020
	if ( ( $time_newcomment - $time_lastcomment ) < 15 ) {
2021
		return true;
2022
	}
2023
	return false;
2024
}
2025
2026
/**
2027
 * Adds a new comment to the database.
2028
 *
2029
 * Filters new comment to ensure that the fields are sanitized and valid before
2030
 * inserting comment into database. Calls {@see 'comment_post'} action with comment ID
2031
 * and whether comment is approved by WordPress. Also has {@see 'preprocess_comment'}
2032
 * filter for processing the comment data before the function handles it.
2033
 *
2034
 * We use `REMOTE_ADDR` here directly. If you are behind a proxy, you should ensure
2035
 * that it is properly set, such as in wp-config.php, for your environment.
2036
 *
2037
 * See {@link https://core.trac.wordpress.org/ticket/9235}
2038
 *
2039
 * @since 1.5.0
2040
 * @since 4.3.0 'comment_agent' and 'comment_author_IP' can be set via `$commentdata`.
2041
 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function to
2042
 *              return a WP_Error object instead of dying.
2043
 *
2044
 * @see wp_insert_comment()
2045
 * @global wpdb $wpdb WordPress database abstraction object.
2046
 *
2047
 * @param array $commentdata {
2048
 *     Comment data.
2049
 *
2050
 *     @type string $comment_author       The name of the comment author.
2051
 *     @type string $comment_author_email The comment author email address.
2052
 *     @type string $comment_author_url   The comment author URL.
2053
 *     @type string $comment_content      The content of the comment.
2054
 *     @type string $comment_date         The date the comment was submitted. Default is the current time.
2055
 *     @type string $comment_date_gmt     The date the comment was submitted in the GMT timezone.
2056
 *                                        Default is `$comment_date` in the GMT timezone.
2057
 *     @type int    $comment_parent       The ID of this comment's parent, if any. Default 0.
2058
 *     @type int    $comment_post_ID      The ID of the post that relates to the comment.
2059
 *     @type int    $user_id              The ID of the user who submitted the comment. Default 0.
2060
 *     @type int    $user_ID              Kept for backward-compatibility. Use `$user_id` instead.
2061
 *     @type string $comment_agent        Comment author user agent. Default is the value of 'HTTP_USER_AGENT'
2062
 *                                        in the `$_SERVER` superglobal sent in the original request.
2063
 *     @type string $comment_author_IP    Comment author IP address in IPv4 format. Default is the value of
2064
 *                                        'REMOTE_ADDR' in the `$_SERVER` superglobal sent in the original request.
2065
 * }
2066
 * @param bool $avoid_die Should errors be returned as WP_Error objects instead of
2067
 *                        executing wp_die()? Default false.
2068
 * @return int|false|WP_Error The ID of the comment on success, false or WP_Error on failure.
2069
 */
2070 3
function wp_new_comment( $commentdata, $avoid_die = false ) {
2071
	global $wpdb;
2072
2073
	if ( isset( $commentdata['user_ID'] ) ) {
2074
		$commentdata['user_ID'] = (int) $commentdata['user_ID'];
2075
		$commentdata['user_id'] = $commentdata['user_ID'];
2076
	}
2077
2078
	$prefiltered_user_id = ( isset( $commentdata['user_id'] ) ) ? (int) $commentdata['user_id'] : 0;
2079
2080
	/**
2081
	 * Filters a comment's data before it is sanitized and inserted into the database.
2082
	 *
2083
	 * @since 1.5.0
2084
	 *
2085
	 * @param array $commentdata Comment data.
2086
	 */
2087
	$commentdata = apply_filters( 'preprocess_comment', $commentdata );
2088
2089
	$commentdata['comment_post_ID'] = (int) $commentdata['comment_post_ID'];
2090
	if ( isset( $commentdata['user_ID'] ) && $prefiltered_user_id !== (int) $commentdata['user_ID'] ) {
2091
		$commentdata['user_ID'] = (int) $commentdata['user_ID'];
2092
		$commentdata['user_id'] = $commentdata['user_ID'];
2093
	} elseif ( isset( $commentdata['user_id'] ) ) {
2094
		$commentdata['user_id'] = (int) $commentdata['user_id'];
2095
	}
2096
2097
	$commentdata['comment_parent'] = isset( $commentdata['comment_parent'] ) ? absint( $commentdata['comment_parent'] ) : 0;
2098
	$parent_status                 = ( 0 < $commentdata['comment_parent'] ) ? wp_get_comment_status( $commentdata['comment_parent'] ) : '';
2099
	$commentdata['comment_parent'] = ( 'approved' == $parent_status || 'unapproved' == $parent_status ) ? $commentdata['comment_parent'] : 0;
2100
2101
	if ( ! isset( $commentdata['comment_author_IP'] ) ) {
2102
		$commentdata['comment_author_IP'] = $_SERVER['REMOTE_ADDR'];
2103
	}
2104
	$commentdata['comment_author_IP'] = preg_replace( '/[^0-9a-fA-F:., ]/', '', $commentdata['comment_author_IP'] );
2105
2106
	if ( ! isset( $commentdata['comment_agent'] ) ) {
2107
		$commentdata['comment_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
2108
	}
2109
	$commentdata['comment_agent'] = substr( $commentdata['comment_agent'], 0, 254 );
2110
2111
	if ( empty( $commentdata['comment_date'] ) ) {
2112
		$commentdata['comment_date'] = current_time( 'mysql' );
2113
	}
2114
2115
	if ( empty( $commentdata['comment_date_gmt'] ) ) {
2116
		$commentdata['comment_date_gmt'] = current_time( 'mysql', 1 );
2117
	}
2118
2119
	$commentdata = wp_filter_comment( $commentdata );
2120
2121
	$commentdata['comment_approved'] = wp_allow_comment( $commentdata, $avoid_die );
2122
	if ( is_wp_error( $commentdata['comment_approved'] ) ) {
2123
		return $commentdata['comment_approved'];
2124
	}
2125
2126
	$comment_ID = wp_insert_comment( $commentdata );
2127
	if ( ! $comment_ID ) {
2128
		$fields = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content' );
2129
2130
		foreach ( $fields as $field ) {
2131
			if ( isset( $commentdata[ $field ] ) ) {
2132
				$commentdata[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->comments, $field, $commentdata[ $field ] );
2133
			}
2134
		}
2135
2136
		$commentdata = wp_filter_comment( $commentdata );
2137
2138
		$commentdata['comment_approved'] = wp_allow_comment( $commentdata, $avoid_die );
2139
		if ( is_wp_error( $commentdata['comment_approved'] ) ) {
2140
			return $commentdata['comment_approved'];
2141
		}
2142
2143
		$comment_ID = wp_insert_comment( $commentdata );
2144
		if ( ! $comment_ID ) {
2145
			return false;
2146
		}
2147
	}
2148
2149
	/**
2150
	 * Fires immediately after a comment is inserted into the database.
2151
	 *
2152
	 * @since 1.2.0
2153
	 * @since 4.5.0 The `$commentdata` parameter was added.
2154
	 *
2155
	 * @param int        $comment_ID       The comment ID.
2156
	 * @param int|string $comment_approved 1 if the comment is approved, 0 if not, 'spam' if spam.
2157
	 * @param array      $commentdata      Comment data.
2158
	 */
2159
	do_action( 'comment_post', $comment_ID, $commentdata['comment_approved'], $commentdata );
2160
2161
	return $comment_ID;
2162
}
2163
2164
/**
2165
 * Send a comment moderation notification to the comment moderator.
2166
 *
2167
 * @since 4.4.0
2168
 *
2169
 * @param int $comment_ID ID of the comment.
2170
 * @return bool True on success, false on failure.
2171
 */
2172
function wp_new_comment_notify_moderator( $comment_ID ) {
2173
	$comment = get_comment( $comment_ID );
2174
2175
	// Only send notifications for pending comments.
2176
	$maybe_notify = ( '0' == $comment->comment_approved );
2177
2178
	/** This filter is documented in wp-includes/comment.php */
2179
	$maybe_notify = apply_filters( 'notify_moderator', $maybe_notify, $comment_ID );
2180
2181
	if ( ! $maybe_notify ) {
2182
		return false;
2183
	}
2184
2185
	return wp_notify_moderator( $comment_ID );
2186
}
2187
2188
/**
2189
 * Send a notification of a new comment to the post author.
2190
 *
2191
 * @since 4.4.0
2192
 *
2193
 * Uses the {@see 'notify_post_author'} filter to determine whether the post author
2194
 * should be notified when a new comment is added, overriding site setting.
2195
 *
2196
 * @param int $comment_ID Comment ID.
2197
 * @return bool True on success, false on failure.
2198
 */
2199
function wp_new_comment_notify_postauthor( $comment_ID ) {
2200
	$comment = get_comment( $comment_ID );
2201
2202
	$maybe_notify = get_option( 'comments_notify' );
2203
2204
	/**
2205
	 * Filters whether to send the post author new comment notification emails,
2206
	 * overriding the site setting.
2207
	 *
2208
	 * @since 4.4.0
2209
	 *
2210
	 * @param bool $maybe_notify Whether to notify the post author about the new comment.
2211
	 * @param int  $comment_ID   The ID of the comment for the notification.
2212
	 */
2213
	$maybe_notify = apply_filters( 'notify_post_author', $maybe_notify, $comment_ID );
2214
2215
	/*
2216
	 * wp_notify_postauthor() checks if notifying the author of their own comment.
2217
	 * By default, it won't, but filters can override this.
2218
	 */
2219
	if ( ! $maybe_notify ) {
2220
		return false;
2221
	}
2222
2223
	// Only send notifications for approved comments.
2224
	if ( ! isset( $comment->comment_approved ) || '1' != $comment->comment_approved ) {
2225
		return false;
2226
	}
2227
2228
	return wp_notify_postauthor( $comment_ID );
2229
}
2230
2231
/**
2232
 * Sets the status of a comment.
2233
 *
2234
 * The {@see 'wp_set_comment_status'} action is called after the comment is handled.
2235
 * If the comment status is not in the list, then false is returned.
2236
 *
2237
 * @since 1.0.0
2238
 *
2239
 * @global wpdb $wpdb WordPress database abstraction object.
2240
 *
2241
 * @param int|WP_Comment $comment_id     Comment ID or WP_Comment object.
2242
 * @param string         $comment_status New comment status, either 'hold', 'approve', 'spam', or 'trash'.
2243
 * @param bool           $wp_error       Whether to return a WP_Error object if there is a failure. Default is false.
2244
 * @return bool|WP_Error True on success, false or WP_Error on failure.
2245
 */
2246 1
function wp_set_comment_status( $comment_id, $comment_status, $wp_error = false ) {
2247
	global $wpdb;
2248
2249
	switch ( $comment_status ) {
2250
		case 'hold':
2251
		case '0':
2252
			$status = '0';
2253
			break;
2254
		case 'approve':
2255
		case '1':
2256
			$status = '1';
2257
			add_action( 'wp_set_comment_status', 'wp_new_comment_notify_postauthor' );
2258
			break;
2259
		case 'spam':
2260
			$status = 'spam';
2261
			break;
2262
		case 'trash':
2263
			$status = 'trash';
2264
			break;
2265
		default:
2266
			return false;
2267
	}
2268
2269
	$comment_old = clone get_comment( $comment_id );
2270
2271
	if ( ! $wpdb->update( $wpdb->comments, array( 'comment_approved' => $status ), array( 'comment_ID' => $comment_old->comment_ID ) ) ) {
2272
		if ( $wp_error ) {
2273
			return new WP_Error( 'db_update_error', __( 'Could not update comment status' ), $wpdb->last_error );
2274
		} else {
2275
			return false;
2276
		}
2277
	}
2278
2279
	clean_comment_cache( $comment_old->comment_ID );
2280
2281
	$comment = get_comment( $comment_old->comment_ID );
2282
2283
	/**
2284
	 * Fires immediately before transitioning a comment's status from one to another
2285
	 * in the database.
2286
	 *
2287
	 * @since 1.5.0
2288
	 *
2289
	 * @param int         $comment_id     Comment ID.
2290
	 * @param string|bool $comment_status Current comment status. Possible values include
2291
	 *                                    'hold', 'approve', 'spam', 'trash', or false.
2292
	 */
2293
	do_action( 'wp_set_comment_status', $comment->comment_ID, $comment_status );
2294
2295
	wp_transition_comment_status( $comment_status, $comment_old->comment_approved, $comment );
2296
2297
	wp_update_comment_count( $comment->comment_post_ID );
2298
2299
	return true;
2300
}
2301
2302
/**
2303
 * Updates an existing comment in the database.
2304
 *
2305
 * Filters the comment and makes sure certain fields are valid before updating.
2306
 *
2307
 * @since 2.0.0
2308
 * @since 4.9.0 Add updating comment meta during comment update.
2309
 *
2310
 * @global wpdb $wpdb WordPress database abstraction object.
2311
 *
2312
 * @param array $commentarr Contains information on the comment.
2313
 * @return int Comment was updated if value is 1, or was not updated if value is 0.
2314
 */
2315 1
function wp_update_comment( $commentarr ) {
2316
	global $wpdb;
2317
2318
	// First, get all of the original fields
2319
	$comment = get_comment( $commentarr['comment_ID'], ARRAY_A );
2320
	if ( empty( $comment ) ) {
2321
		return 0;
2322
	}
2323
2324
	// Make sure that the comment post ID is valid (if specified).
2325
	if ( ! empty( $commentarr['comment_post_ID'] ) && ! get_post( $commentarr['comment_post_ID'] ) ) {
2326
		return 0;
2327
	}
2328
2329
	// Escape data pulled from DB.
2330
	$comment = wp_slash( $comment );
2331
2332
	$old_status = $comment['comment_approved'];
2333
2334
	// Merge old and new fields with new fields overwriting old ones.
2335
	$commentarr = array_merge( $comment, $commentarr );
2336
2337
	$commentarr = wp_filter_comment( $commentarr );
2338
2339
	// Now extract the merged array.
2340
	$data = wp_unslash( $commentarr );
2341
2342
	/**
2343
	 * Filters the comment content before it is updated in the database.
2344
	 *
2345
	 * @since 1.5.0
2346
	 *
2347
	 * @param string $comment_content The comment data.
2348
	 */
2349
	$data['comment_content'] = apply_filters( 'comment_save_pre', $data['comment_content'] );
2350
2351
	$data['comment_date_gmt'] = get_gmt_from_date( $data['comment_date'] );
2352
2353
	if ( ! isset( $data['comment_approved'] ) ) {
2354
		$data['comment_approved'] = 1;
2355
	} elseif ( 'hold' == $data['comment_approved'] ) {
2356
		$data['comment_approved'] = 0;
2357
	} elseif ( 'approve' == $data['comment_approved'] ) {
2358
		$data['comment_approved'] = 1;
2359
	}
2360
2361
	$comment_ID      = $data['comment_ID'];
2362
	$comment_post_ID = $data['comment_post_ID'];
2363
2364
	/**
2365
	 * Filters the comment data immediately before it is updated in the database.
2366
	 *
2367
	 * Note: data being passed to the filter is already unslashed.
2368
	 *
2369
	 * @since 4.7.0
2370
	 *
2371
	 * @param array $data       The new, processed comment data.
2372
	 * @param array $comment    The old, unslashed comment data.
2373
	 * @param array $commentarr The new, raw comment data.
2374
	 */
2375
	$data = apply_filters( 'wp_update_comment_data', $data, $comment, $commentarr );
2376
2377
	$keys = array( 'comment_post_ID', 'comment_content', 'comment_author', 'comment_author_email', 'comment_approved', 'comment_karma', 'comment_author_url', 'comment_date', 'comment_date_gmt', 'comment_type', 'comment_parent', 'user_id', 'comment_agent', 'comment_author_IP' );
2378
	$data = wp_array_slice_assoc( $data, $keys );
2379
2380
	$rval = $wpdb->update( $wpdb->comments, $data, compact( 'comment_ID' ) );
2381
2382
	// If metadata is provided, store it.
2383
	if ( isset( $commentarr['comment_meta'] ) && is_array( $commentarr['comment_meta'] ) ) {
2384
		foreach ( $commentarr['comment_meta'] as $meta_key => $meta_value ) {
2385
			update_comment_meta( $comment_ID, $meta_key, $meta_value );
2386
		}
2387
	}
2388
2389
	clean_comment_cache( $comment_ID );
2390
	wp_update_comment_count( $comment_post_ID );
2391
	/**
2392
	 * Fires immediately after a comment is updated in the database.
2393
	 *
2394
	 * The hook also fires immediately before comment status transition hooks are fired.
2395
	 *
2396
	 * @since 1.2.0
2397
	 * @since 4.6.0 Added the `$data` parameter.
2398
	 *
2399
	 * @param int   $comment_ID The comment ID.
2400
	 * @param array $data       Comment data.
2401
	 */
2402
	do_action( 'edit_comment', $comment_ID, $data );
2403
	$comment = get_comment( $comment_ID );
2404
	wp_transition_comment_status( $comment->comment_approved, $old_status, $comment );
2405
	return $rval;
2406
}
2407
2408
/**
2409
 * Whether to defer comment counting.
2410
 *
2411
 * When setting $defer to true, all post comment counts will not be updated
2412
 * until $defer is set to false. When $defer is set to false, then all
2413
 * previously deferred updated post comment counts will then be automatically
2414
 * updated without having to call wp_update_comment_count() after.
2415
 *
2416
 * @since 2.5.0
2417
 * @staticvar bool $_defer
2418
 *
2419
 * @param bool $defer
2420
 * @return bool
2421
 */
2422
function wp_defer_comment_counting( $defer = null ) {
2423
	static $_defer = false;
2424
2425
	if ( is_bool( $defer ) ) {
2426
		$_defer = $defer;
2427
		// flush any deferred counts
2428
		if ( ! $defer ) {
2429
			wp_update_comment_count( null, true );
2430
		}
2431
	}
2432
2433
	return $_defer;
2434
}
2435
2436
/**
2437
 * Updates the comment count for post(s).
2438
 *
2439
 * When $do_deferred is false (is by default) and the comments have been set to
2440
 * be deferred, the post_id will be added to a queue, which will be updated at a
2441
 * later date and only updated once per post ID.
2442
 *
2443
 * If the comments have not be set up to be deferred, then the post will be
2444
 * updated. When $do_deferred is set to true, then all previous deferred post
2445
 * IDs will be updated along with the current $post_id.
2446
 *
2447
 * @since 2.1.0
2448
 * @see wp_update_comment_count_now() For what could cause a false return value
2449
 *
2450
 * @staticvar array $_deferred
2451
 *
2452
 * @param int|null $post_id     Post ID.
2453
 * @param bool     $do_deferred Optional. Whether to process previously deferred
2454
 *                              post comment counts. Default false.
2455
 * @return bool|void True on success, false on failure or if post with ID does
2456
 *                   not exist.
2457
 */
2458 1
function wp_update_comment_count( $post_id, $do_deferred = false ) {
2459
	static $_deferred = array();
2460
2461
	if ( empty( $post_id ) && ! $do_deferred ) {
2462
		return false;
2463
	}
2464
2465
	if ( $do_deferred ) {
2466
		$_deferred = array_unique( $_deferred );
2467
		foreach ( $_deferred as $i => $_post_id ) {
2468
			wp_update_comment_count_now( $_post_id );
2469
			unset( $_deferred[ $i ] );
2470
			/** @todo Move this outside of the foreach and reset $_deferred to an array instead */
2471
		}
2472
	}
2473
2474
	if ( wp_defer_comment_counting() ) {
2475
		$_deferred[] = $post_id;
2476
		return true;
2477
	} elseif ( $post_id ) {
2478
		return wp_update_comment_count_now( $post_id );
2479
	}
2480
2481
}
2482
2483
/**
2484
 * Updates the comment count for the post.
2485
 *
2486
 * @since 2.5.0
2487
 *
2488
 * @global wpdb $wpdb WordPress database abstraction object.
2489
 *
2490
 * @param int $post_id Post ID
2491
 * @return bool True on success, false if the post does not exist.
2492
 */
2493
function wp_update_comment_count_now( $post_id ) {
2494
	global $wpdb;
2495
	$post_id = (int) $post_id;
2496
	if ( ! $post_id ) {
2497
		return false;
2498
	}
2499
2500
	wp_cache_delete( 'comments-0', 'counts' );
2501
	wp_cache_delete( "comments-{$post_id}", 'counts' );
2502
2503
	$post = get_post( $post_id );
2504
	if ( ! $post ) {
2505
		return false;
2506
	}
2507
2508
	$old = (int) $post->comment_count;
2509
2510
	/**
2511
	 * Filters a post's comment count before it is updated in the database.
2512
	 *
2513
	 * @since 4.5.0
2514
	 *
2515
	 * @param int|null $new     The new comment count. Default null.
2516
	 * @param int      $old     The old comment count.
2517
	 * @param int      $post_id Post ID.
2518
	 */
2519
	$new = apply_filters( 'pre_wp_update_comment_count_now', null, $old, $post_id );
2520
2521
	if ( is_null( $new ) ) {
2522
		$new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1'", $post_id ) );
2523
	} else {
2524
		$new = (int) $new;
2525
	}
2526
2527
	$wpdb->update( $wpdb->posts, array( 'comment_count' => $new ), array( 'ID' => $post_id ) );
2528
2529
	clean_post_cache( $post );
2530
2531
	/**
2532
	 * Fires immediately after a post's comment count is updated in the database.
2533
	 *
2534
	 * @since 2.3.0
2535
	 *
2536
	 * @param int $post_id Post ID.
2537
	 * @param int $new     The new comment count.
2538
	 * @param int $old     The old comment count.
2539
	 */
2540
	do_action( 'wp_update_comment_count', $post_id, $new, $old );
2541
2542
	/** This action is documented in wp-includes/post.php */
2543
	do_action( "edit_post_{$post->post_type}", $post_id, $post );
2544
2545
	/** This action is documented in wp-includes/post.php */
2546
	do_action( 'edit_post', $post_id, $post );
2547
2548
	return true;
2549
}
2550
2551
//
2552
// Ping and trackback functions.
2553
//
2554
2555
/**
2556
 * Finds a pingback server URI based on the given URL.
2557
 *
2558
 * Checks the HTML for the rel="pingback" link and x-pingback headers. It does
2559
 * a check for the x-pingback headers first and returns that, if available. The
2560
 * check for the rel="pingback" has more overhead than just the header.
2561
 *
2562
 * @since 1.5.0
2563
 *
2564
 * @param string $url URL to ping.
2565
 * @param int $deprecated Not Used.
2566
 * @return false|string False on failure, string containing URI on success.
2567
 */
2568 1
function discover_pingback_server_uri( $url, $deprecated = '' ) {
2569
	if ( ! empty( $deprecated ) ) {
2570
		_deprecated_argument( __FUNCTION__, '2.7.0' );
2571
	}
2572
2573
	$pingback_str_dquote = 'rel="pingback"';
2574
	$pingback_str_squote = 'rel=\'pingback\'';
2575
2576
	/** @todo Should use Filter Extension or custom preg_match instead. */
2577
	$parsed_url = parse_url( $url );
2578
2579
	if ( ! isset( $parsed_url['host'] ) ) { // Not a URL. This should never happen.
2580
		return false;
2581
	}
2582
2583
	//Do not search for a pingback server on our own uploads
2584
	$uploads_dir = wp_get_upload_dir();
2585
	if ( 0 === strpos( $url, $uploads_dir['baseurl'] ) ) {
2586
		return false;
2587
	}
2588
2589
	$response = wp_safe_remote_head(
2590
		$url,
2591
		array(
2592
			'timeout'     => 2,
2593
			'httpversion' => '1.0',
2594
		)
2595
	);
2596
2597
	if ( is_wp_error( $response ) ) {
2598
		return false;
2599
	}
2600
2601
	if ( wp_remote_retrieve_header( $response, 'x-pingback' ) ) {
2602
		return wp_remote_retrieve_header( $response, 'x-pingback' );
2603
	}
2604
2605
	// Not an (x)html, sgml, or xml page, no use going further.
2606
	if ( preg_match( '#(image|audio|video|model)/#is', wp_remote_retrieve_header( $response, 'content-type' ) ) ) {
2607
		return false;
2608
	}
2609
2610
	// Now do a GET since we're going to look in the html headers (and we're sure it's not a binary file)
2611
	$response = wp_safe_remote_get(
2612
		$url,
2613
		array(
2614
			'timeout'     => 2,
2615
			'httpversion' => '1.0',
2616
		)
2617
	);
2618
2619
	if ( is_wp_error( $response ) ) {
2620
		return false;
2621
	}
2622
2623
	$contents = wp_remote_retrieve_body( $response );
2624
2625 1
	$pingback_link_offset_dquote = strpos( $contents, $pingback_str_dquote );
2626 1
	$pingback_link_offset_squote = strpos( $contents, $pingback_str_squote );
2627
	if ( $pingback_link_offset_dquote || $pingback_link_offset_squote ) {
2628
		$quote                   = ( $pingback_link_offset_dquote ) ? '"' : '\'';
2629
		$pingback_link_offset    = ( $quote == '"' ) ? $pingback_link_offset_dquote : $pingback_link_offset_squote;
2630
		$pingback_href_pos       = strpos( $contents, 'href=', $pingback_link_offset );
2631
		$pingback_href_start     = $pingback_href_pos + 6;
2632
		$pingback_href_end       = strpos( $contents, $quote, $pingback_href_start );
2633 1
		$pingback_server_url_len = $pingback_href_end - $pingback_href_start;
2634
		$pingback_server_url     = substr( $contents, $pingback_href_start, $pingback_server_url_len );
2635
2636
		// We may find rel="pingback" but an incomplete pingback URL
2637
		if ( $pingback_server_url_len > 0 ) { // We got it!
2638
			return $pingback_server_url;
2639
		}
2640
	}
2641
2642
	return false;
2643
}
2644
2645
/**
2646
 * Perform all pingbacks, enclosures, trackbacks, and send to pingback services.
2647
 *
2648
 * @since 2.1.0
2649
 *
2650
 * @global wpdb $wpdb WordPress database abstraction object.
2651
 */
2652
function do_all_pings() {
2653 1
	global $wpdb;
2654
2655
	// Do pingbacks.
2656
	$pings = get_posts(
2657
		array(
2658
			'post_type'        => get_post_types(),
2659
			'suppress_filters' => false,
2660
			'nopaging'         => true,
2661
			'meta_key'         => '_pingme',
2662
			'fields'           => 'ids',
2663
		)
2664
	);
2665
2666
	foreach ( $pings as $ping ) {
2667
		delete_post_meta( $ping, '_pingme' );
2668
		pingback( null, $ping );
2669
	}
2670
2671
	// Do enclosures.
2672
	$enclosures = get_posts(
2673
		array(
2674
			'post_type'        => get_post_types(),
2675
			'suppress_filters' => false,
2676
			'nopaging'         => true,
2677
			'meta_key'         => '_encloseme',
2678
			'fields'           => 'ids',
2679
		)
2680
	);
2681
2682
	foreach ( $enclosures as $enclosure ) {
2683
		delete_post_meta( $enclosure, '_encloseme' );
2684
		do_enclose( null, $enclosure );
2685
	}
2686
2687
	// Do trackbacks.
2688
	$trackbacks = get_posts(
2689
		array(
2690
			'post_type'        => get_post_types(),
2691
			'suppress_filters' => false,
2692
			'nopaging'         => true,
2693
			'meta_key'         => '_trackbackme',
2694
			'fields'           => 'ids',
2695
		)
2696
	);
2697
2698
	foreach ( $trackbacks as $trackback ) {
2699
		delete_post_meta( $trackback, '_trackbackme' );
2700
		do_trackbacks( $trackback );
2701
	}
2702
2703
	// Do Update Services/Generic Pings.
2704
	generic_ping();
2705
}
2706
2707
/**
2708
 * Perform trackbacks.
2709
 *
2710
 * @since 1.5.0
2711
 * @since 4.7.0 `$post_id` can be a WP_Post object.
2712
 *
2713
 * @global wpdb $wpdb WordPress database abstraction object.
2714
 *
2715
 * @param int|WP_Post $post_id Post object or ID to do trackbacks on.
2716
 */
2717
function do_trackbacks( $post_id ) {
2718
	global $wpdb;
2719
	$post = get_post( $post_id );
2720
	if ( ! $post ) {
2721
		return false;
2722
	}
2723
2724
	$to_ping = get_to_ping( $post );
2725
	$pinged  = get_pung( $post );
2726
	if ( empty( $to_ping ) ) {
2727
		$wpdb->update( $wpdb->posts, array( 'to_ping' => '' ), array( 'ID' => $post->ID ) );
2728
		return;
2729
	}
2730
2731
	if ( empty( $post->post_excerpt ) ) {
2732
		/** This filter is documented in wp-includes/post-template.php */
2733
		$excerpt = apply_filters( 'the_content', $post->post_content, $post->ID );
2734
	} else {
2735
		/** This filter is documented in wp-includes/post-template.php */
2736
		$excerpt = apply_filters( 'the_excerpt', $post->post_excerpt );
2737
	}
2738
2739
	$excerpt = str_replace( ']]>', ']]&gt;', $excerpt );
2740
	$excerpt = wp_html_excerpt( $excerpt, 252, '&#8230;' );
2741
2742
	/** This filter is documented in wp-includes/post-template.php */
2743
	$post_title = apply_filters( 'the_title', $post->post_title, $post->ID );
2744
	$post_title = strip_tags( $post_title );
2745
2746
	if ( $to_ping ) {
2747
		foreach ( (array) $to_ping as $tb_ping ) {
2748
			$tb_ping = trim( $tb_ping );
2749
			if ( ! in_array( $tb_ping, $pinged ) ) {
2750
				trackback( $tb_ping, $post_title, $excerpt, $post->ID );
2751
				$pinged[] = $tb_ping;
2752
			} else {
2753
				$wpdb->query(
2754
					$wpdb->prepare(
2755
						"UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s,
2756
					'')) WHERE ID = %d",
2757
						$tb_ping,
2758
						$post->ID
2759
					)
2760
				);
2761
			}
2762
		}
2763
	}
2764
}
2765
2766
/**
2767
 * Sends pings to all of the ping site services.
2768
 *
2769
 * @since 1.2.0
2770
 *
2771
 * @param int $post_id Post ID.
2772
 * @return int Same as Post ID from parameter
2773
 */
2774
function generic_ping( $post_id = 0 ) {
2775
	$services = get_option( 'ping_sites' );
2776
2777
	$services = explode( "\n", $services );
2778
	foreach ( (array) $services as $service ) {
2779
		$service = trim( $service );
2780
		if ( '' != $service ) {
2781
			weblog_ping( $service );
2782
		}
2783
	}
2784
2785
	return $post_id;
2786
}
2787
2788
/**
2789
 * Pings back the links found in a post.
2790
 *
2791
 * @since 0.71
2792
 * @since 4.7.0 `$post_id` can be a WP_Post object.
2793
 *
2794
 * @param string $content Post content to check for links. If empty will retrieve from post.
2795
 * @param int|WP_Post $post_id Post Object or ID.
2796
 */
2797 1
function pingback( $content, $post_id ) {
2798
	include_once( ABSPATH . WPINC . '/class-IXR.php' );
2799
	include_once( ABSPATH . WPINC . '/class-wp-http-ixr-client.php' );
2800
2801
	// original code by Mort (http://mort.mine.nu:8080)
2802
	$post_links = array();
2803
2804
	$post = get_post( $post_id );
2805
	if ( ! $post ) {
2806
		return;
2807
	}
2808
2809
	$pung = get_pung( $post );
2810
2811
	if ( empty( $content ) ) {
2812
		$content = $post->post_content;
2813
	}
2814
2815
	// Step 1
2816
	// Parsing the post, external links (if any) are stored in the $post_links array
2817
	$post_links_temp = wp_extract_urls( $content );
2818
2819
	// Step 2.
2820
	// Walking thru the links array
2821
	// first we get rid of links pointing to sites, not to specific files
2822
	// Example:
2823
	// http://dummy-weblog.org
2824
	// http://dummy-weblog.org/
2825
	// http://dummy-weblog.org/post.php
2826
	// We don't wanna ping first and second types, even if they have a valid <link/>
2827
2828
	foreach ( (array) $post_links_temp as $link_test ) :
2829
		if ( ! in_array( $link_test, $pung ) && ( url_to_postid( $link_test ) != $post->ID ) // If we haven't pung it already and it isn't a link to itself
2830
				&& ! is_local_attachment( $link_test ) ) : // Also, let's never ping local attachments.
2831
			$test = @parse_url( $link_test );
2832
			if ( $test ) {
2833
				if ( isset( $test['query'] ) ) {
2834
					$post_links[] = $link_test;
2835
				} elseif ( isset( $test['path'] ) && ( $test['path'] != '/' ) && ( $test['path'] != '' ) ) {
2836
					$post_links[] = $link_test;
2837
				}
2838
			}
2839
		endif;
2840
	endforeach;
2841
2842
	$post_links = array_unique( $post_links );
2843
	/**
2844
	 * Fires just before pinging back links found in a post.
2845
	 *
2846
	 * @since 2.0.0
2847
	 *
2848
	 * @param string[] $post_links Array of link URLs to be checked (passed by reference).
2849
	 * @param string[] $pung       Array of link URLs already pinged (passed by reference).
2850
	 * @param int      $post_ID    The post ID.
2851
	 */
2852
	do_action_ref_array( 'pre_ping', array( &$post_links, &$pung, $post->ID ) );
2853
2854
	foreach ( (array) $post_links as $pagelinkedto ) {
2855
		$pingback_server_url = discover_pingback_server_uri( $pagelinkedto );
2856
2857
		if ( $pingback_server_url ) {
2858
			set_time_limit( 60 );
2859
			// Now, the RPC call
2860
			$pagelinkedfrom = get_permalink( $post );
2861
2862
			// using a timeout of 3 seconds should be enough to cover slow servers
2863
			$client          = new WP_HTTP_IXR_Client( $pingback_server_url );
2864
			$client->timeout = 3;
2865
			/**
2866
			 * Filters the user agent sent when pinging-back a URL.
2867
			 *
2868
			 * @since 2.9.0
2869
			 *
2870
			 * @param string $concat_useragent    The user agent concatenated with ' -- WordPress/'
2871
			 *                                    and the WordPress version.
2872
			 * @param string $useragent           The useragent.
2873
			 * @param string $pingback_server_url The server URL being linked to.
2874
			 * @param string $pagelinkedto        URL of page linked to.
2875
			 * @param string $pagelinkedfrom      URL of page linked from.
2876
			 */
2877
			$client->useragent = apply_filters( 'pingback_useragent', $client->useragent . ' -- WordPress/' . get_bloginfo( 'version' ), $client->useragent, $pingback_server_url, $pagelinkedto, $pagelinkedfrom );
2878
			// when set to true, this outputs debug messages by itself
2879
			$client->debug = false;
2880
2881
			if ( $client->query( 'pingback.ping', $pagelinkedfrom, $pagelinkedto ) || ( isset( $client->error->code ) && 48 == $client->error->code ) ) { // Already registered
2882
				add_ping( $post, $pagelinkedto );
2883
			}
2884
		}
2885
	}
2886
}
2887
2888
/**
2889
 * Check whether blog is public before returning sites.
2890
 *
2891
 * @since 2.1.0
2892
 *
2893
 * @param mixed $sites Will return if blog is public, will not return if not public.
2894
 * @return mixed Empty string if blog is not public, returns $sites, if site is public.
2895
 */
2896
function privacy_ping_filter( $sites ) {
2897
	if ( '0' != get_option( 'blog_public' ) ) {
2898
		return $sites;
2899
	} else {
2900
		return '';
2901
	}
2902
}
2903
2904
/**
2905
 * Send a Trackback.
2906
 *
2907
 * Updates database when sending trackback to prevent duplicates.
2908
 *
2909
 * @since 0.71
2910
 *
2911
 * @global wpdb $wpdb WordPress database abstraction object.
2912
 *
2913
 * @param string $trackback_url URL to send trackbacks.
2914
 * @param string $title Title of post.
2915
 * @param string $excerpt Excerpt of post.
2916
 * @param int $ID Post ID.
2917
 * @return int|false|void Database query from update.
2918
 */
2919 1
function trackback( $trackback_url, $title, $excerpt, $ID ) {
2920
	global $wpdb;
2921
2922
	if ( empty( $trackback_url ) ) {
2923
		return;
2924
	}
2925
2926
	$options            = array();
2927
	$options['timeout'] = 10;
2928
	$options['body']    = array(
2929
		'title'     => $title,
2930
		'url'       => get_permalink( $ID ),
2931
		'blog_name' => get_option( 'blogname' ),
2932
		'excerpt'   => $excerpt,
2933
	);
2934
2935
	$response = wp_safe_remote_post( $trackback_url, $options );
2936
2937
	if ( is_wp_error( $response ) ) {
2938
		return;
2939
	}
2940
2941
	$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', %s) WHERE ID = %d", $trackback_url, $ID ) );
2942
	return $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $trackback_url, $ID ) );
2943
}
2944
2945
/**
2946
 * Send a pingback.
2947
 *
2948
 * @since 1.2.0
2949
 *
2950
 * @param string $server Host of blog to connect to.
2951
 * @param string $path Path to send the ping.
2952
 */
2953
function weblog_ping( $server = '', $path = '' ) {
2954
	include_once( ABSPATH . WPINC . '/class-IXR.php' );
2955
	include_once( ABSPATH . WPINC . '/class-wp-http-ixr-client.php' );
2956
2957
	// using a timeout of 3 seconds should be enough to cover slow servers
2958
	$client             = new WP_HTTP_IXR_Client( $server, ( ( ! strlen( trim( $path ) ) || ( '/' == $path ) ) ? false : $path ) );
2959
	$client->timeout    = 3;
2960
	$client->useragent .= ' -- WordPress/' . get_bloginfo( 'version' );
2961
2962
	// when set to true, this outputs debug messages by itself
2963
	$client->debug = false;
2964
	$home          = trailingslashit( home_url() );
2965
	if ( ! $client->query( 'weblogUpdates.extendedPing', get_option( 'blogname' ), $home, get_bloginfo( 'rss2_url' ) ) ) { // then try a normal ping
2966
		$client->query( 'weblogUpdates.ping', get_option( 'blogname' ), $home );
2967
	}
2968
}
2969
2970
/**
2971
 * Default filter attached to pingback_ping_source_uri to validate the pingback's Source URI
2972
 *
2973
 * @since 3.5.1
2974
 * @see wp_http_validate_url()
2975
 *
2976
 * @param string $source_uri
2977
 * @return string
2978
 */
2979
function pingback_ping_source_uri( $source_uri ) {
2980
	return (string) wp_http_validate_url( $source_uri );
2981
}
2982
2983
/**
2984
 * Default filter attached to xmlrpc_pingback_error.
2985
 *
2986
 * Returns a generic pingback error code unless the error code is 48,
2987
 * which reports that the pingback is already registered.
2988
 *
2989
 * @since 3.5.1
2990
 * @link https://www.hixie.ch/specs/pingback/pingback#TOC3
2991
 *
2992
 * @param IXR_Error $ixr_error
2993
 * @return IXR_Error
2994
 */
2995
function xmlrpc_pingback_error( $ixr_error ) {
2996
	if ( $ixr_error->code === 48 ) {
2997
		return $ixr_error;
2998
	}
2999
	return new IXR_Error( 0, '' );
3000
}
3001
3002
//
3003
// Cache
3004
//
3005
3006
/**
3007
 * Removes a comment from the object cache.
3008
 *
3009
 * @since 2.3.0
3010
 *
3011
 * @param int|array $ids Comment ID or an array of comment IDs to remove from cache.
3012
 */
3013
function clean_comment_cache( $ids ) {
3014
	foreach ( (array) $ids as $id ) {
3015
		wp_cache_delete( $id, 'comment' );
3016
3017
		/**
3018
		 * Fires immediately after a comment has been removed from the object cache.
3019
		 *
3020
		 * @since 4.5.0
3021
		 *
3022
		 * @param int $id Comment ID.
3023
		 */
3024
		do_action( 'clean_comment_cache', $id );
3025
	}
3026
3027
	wp_cache_set( 'last_changed', microtime(), 'comment' );
3028
}
3029
3030
/**
3031
 * Updates the comment cache of given comments.
3032
 *
3033
 * Will add the comments in $comments to the cache. If comment ID already exists
3034
 * in the comment cache then it will not be updated. The comment is added to the
3035
 * cache using the comment group with the key using the ID of the comments.
3036
 *
3037
 * @since 2.3.0
3038
 * @since 4.4.0 Introduced the `$update_meta_cache` parameter.
3039
 *
3040
 * @param WP_Comment[] $comments          Array of comment objects
3041
 * @param bool         $update_meta_cache Whether to update commentmeta cache. Default true.
3042
 */
3043 1
function update_comment_cache( $comments, $update_meta_cache = true ) {
3044
	foreach ( (array) $comments as $comment ) {
3045
		wp_cache_add( $comment->comment_ID, $comment, 'comment' );
3046
	}
3047
3048
	if ( $update_meta_cache ) {
3049
		// Avoid `wp_list_pluck()` in case `$comments` is passed by reference.
3050
		$comment_ids = array();
3051
		foreach ( $comments as $comment ) {
3052
			$comment_ids[] = $comment->comment_ID;
3053
		}
3054
		update_meta_cache( 'comment', $comment_ids );
3055
	}
3056
}
3057
3058
/**
3059
 * Adds any comments from the given IDs to the cache that do not already exist in cache.
3060
 *