comment.php 112 KB
Newer Older
godog's avatar
godog committed
1
2
<?php
/**
lechuck's avatar
lechuck committed
3
 * Core Comment API
godog's avatar
godog committed
4
5
6
7
8
9
 *
 * @package WordPress
 * @subpackage Comment
 */

/**
lechuck's avatar
lechuck committed
10
 * Check whether a comment passes internal checks to be allowed to add.
godog's avatar
godog committed
11
 *
lechuck's avatar
lechuck committed
12
13
14
 * If manual comment moderation is set in the administration, then all checks,
 * regardless of their type and whitelist, will fail and the function will
 * return false.
godog's avatar
godog committed
15
16
17
18
19
 *
 * If the number of links exceeds the amount in the administration, then the
 * check fails. If any of the parameter contents match the blacklist of words,
 * then the check fails.
 *
lechuck's avatar
lechuck committed
20
21
 * If the comment author was approved before, then the comment is automatically
 * whitelisted.
godog's avatar
godog committed
22
 *
lechuck's avatar
lechuck committed
23
 * If all checks pass, the function will return true.
godog's avatar
godog committed
24
25
 *
 * @since 1.2.0
lechuck's avatar
lechuck committed
26
27
28
29
30
31
32
33
34
35
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param string $author       Comment author name.
 * @param string $email        Comment author email.
 * @param string $url          Comment author URL.
 * @param string $comment      Content of the comment.
 * @param string $user_ip      Comment author IP address.
 * @param string $user_agent   Comment author User-Agent.
 * @param string $comment_type Comment type, either user-submitted comment,
samba's avatar
samba committed
36
 *                             trackback, or pingback.
lechuck's avatar
lechuck committed
37
 * @return bool If all checks pass, true, otherwise false.
godog's avatar
godog committed
38
 */
samba's avatar
samba committed
39
function check_comment( $author, $email, $url, $comment, $user_ip, $user_agent, $comment_type ) {
godog's avatar
godog committed
40
41
	global $wpdb;

lechuck's avatar
lechuck committed
42
	// If manual moderation is enabled, skip all checks and return false.
samba's avatar
samba committed
43
	if ( 1 == get_option( 'comment_moderation' ) ) {
lechuck's avatar
lechuck committed
44
		return false;
samba's avatar
samba committed
45
	}
godog's avatar
godog committed
46

lucha's avatar
lucha committed
47
	/** This filter is documented in wp-includes/comment-template.php */
lucha's avatar
lucha committed
48
	$comment = apply_filters( 'comment_text', $comment, null, array() );
root's avatar
root committed
49

lechuck's avatar
lechuck committed
50
	// Check for the number of external links if a max allowed number is set.
godog's avatar
godog committed
51
	if ( $max_links = get_option( 'comment_max_links' ) ) {
root's avatar
root committed
52
		$num_links = preg_match_all( '/<a [^>]*href/i', $comment, $out );
lechuck's avatar
lechuck committed
53

lucha's avatar
lucha committed
54
		/**
lucha's avatar
lucha committed
55
		 * Filters the number of links found in a comment.
lucha's avatar
lucha committed
56
57
		 *
		 * @since 3.0.0
lucha's avatar
lucha committed
58
		 * @since 4.7.0 Added the `$comment` parameter.
lucha's avatar
lucha committed
59
		 *
lucha's avatar
lucha committed
60
		 * @param int    $num_links The number of links found.
lucha's avatar
lucha committed
61
		 * @param string $url       Comment author's URL. Included in allowed links total.
lucha's avatar
lucha committed
62
		 * @param string $comment   Content of the comment.
lucha's avatar
lucha committed
63
		 */
lucha's avatar
lucha committed
64
		$num_links = apply_filters( 'comment_max_links_url', $num_links, $url, $comment );
lechuck's avatar
lechuck committed
65
66
67
68
69

		/*
		 * If the number of links in the comment exceeds the allowed amount,
		 * fail the check by returning false.
		 */
samba's avatar
samba committed
70
		if ( $num_links >= $max_links ) {
godog's avatar
godog committed
71
			return false;
samba's avatar
samba committed
72
		}
godog's avatar
godog committed
73
74
	}

samba's avatar
samba committed
75
	$mod_keys = trim( get_option( 'moderation_keys' ) );
lechuck's avatar
lechuck committed
76
77

	// If moderation 'keys' (keywords) are set, process them.
samba's avatar
samba committed
78
79
	if ( ! empty( $mod_keys ) ) {
		$words = explode( "\n", $mod_keys );
godog's avatar
godog committed
80

samba's avatar
samba committed
81
82
		foreach ( (array) $words as $word ) {
			$word = trim( $word );
godog's avatar
godog committed
83

lechuck's avatar
lechuck committed
84
			// Skip empty lines.
samba's avatar
samba committed
85
			if ( empty( $word ) ) {
godog's avatar
godog committed
86
				continue;
samba's avatar
samba committed
87
			}
godog's avatar
godog committed
88

lechuck's avatar
lechuck committed
89
90
91
92
			/*
			 * Do some escaping magic so that '#' (number of) characters in the spam
			 * words don't break things:
			 */
samba's avatar
samba committed
93
			$word = preg_quote( $word, '#' );
godog's avatar
godog committed
94

lechuck's avatar
lechuck committed
95
96
97
98
			/*
			 * Check the comment fields for moderation keywords. If any are found,
			 * fail the check for the given field by returning false.
			 */
godog's avatar
godog committed
99
			$pattern = "#$word#i";
samba's avatar
samba committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
			if ( preg_match( $pattern, $author ) ) {
				return false;
			}
			if ( preg_match( $pattern, $email ) ) {
				return false;
			}
			if ( preg_match( $pattern, $url ) ) {
				return false;
			}
			if ( preg_match( $pattern, $comment ) ) {
				return false;
			}
			if ( preg_match( $pattern, $user_ip ) ) {
				return false;
			}
			if ( preg_match( $pattern, $user_agent ) ) {
				return false;
			}
godog's avatar
godog committed
118
119
120
		}
	}

lechuck's avatar
lechuck committed
121
122
123
124
125
126
127
	/*
	 * Check if the option to approve comments by previously-approved authors is enabled.
	 *
	 * If it is enabled, check whether the comment author has a previously-approved comment,
	 * as well as whether there are any moderation keywords (if set) present in the author
	 * email address. If both checks pass, return true. Otherwise, return false.
	 */
samba's avatar
samba committed
128
	if ( 1 == get_option( 'comment_whitelist' ) ) {
root's avatar
root committed
129
		if ( 'trackback' != $comment_type && 'pingback' != $comment_type && $author != '' && $email != '' ) {
lucha's avatar
lucha committed
130
131
132
133
134
135
136
			$comment_user = get_user_by( 'email', wp_unslash( $email ) );
			if ( ! empty( $comment_user->ID ) ) {
				$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 ) );
			} else {
				// expected_slashed ($author, $email)
				$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 ) );
			}
godog's avatar
godog committed
137
			if ( ( 1 == $ok_to_comment ) &&
samba's avatar
samba committed
138
				( empty( $mod_keys ) || false === strpos( $email, $mod_keys ) ) ) {
godog's avatar
godog committed
139
					return true;
samba's avatar
samba committed
140
			} else {
godog's avatar
godog committed
141
				return false;
samba's avatar
samba committed
142
			}
godog's avatar
godog committed
143
144
145
146
147
148
149
150
151
152
153
		} else {
			return false;
		}
	}
	return true;
}

/**
 * Retrieve the approved comments for post $post_id.
 *
 * @since 2.0.0
lucha's avatar
lucha committed
154
 * @since 4.1.0 Refactored to leverage WP_Comment_Query over a direct query.
godog's avatar
godog committed
155
 *
lechuck's avatar
lechuck committed
156
 * @param  int   $post_id The ID of the post.
lucha's avatar
lucha committed
157
 * @param  array $args    Optional. See WP_Comment_Query::__construct() for information on accepted arguments.
lechuck's avatar
lechuck committed
158
159
 * @return int|array $comments The approved comments, or number of comments if `$count`
 *                             argument is true.
godog's avatar
godog committed
160
 */
lechuck's avatar
lechuck committed
161
162
163
164
165
166
167
168
169
170
function get_approved_comments( $post_id, $args = array() ) {
	if ( ! $post_id ) {
		return array();
	}

	$defaults = array(
		'status'  => 1,
		'post_id' => $post_id,
		'order'   => 'ASC',
	);
samba's avatar
samba committed
171
	$r        = wp_parse_args( $args, $defaults );
lechuck's avatar
lechuck committed
172
173
174

	$query = new WP_Comment_Query;
	return $query->query( $r );
godog's avatar
godog committed
175
176
177
178
179
180
181
182
183
184
}

/**
 * Retrieves comment data given a comment ID or comment object.
 *
 * If an object is passed then the comment data will be cached and then returned
 * after being passed through a filter. If the comment is empty, then the global
 * comment variable will be used, if it is set.
 *
 * @since 2.0.0
lechuck's avatar
lechuck committed
185
 *
lechuck's avatar
lechuck committed
186
 * @global WP_Comment $comment
godog's avatar
godog committed
187
 *
lechuck's avatar
lechuck committed
188
 * @param WP_Comment|string|int $comment Comment to retrieve.
lucha's avatar
lucha committed
189
190
 * @param string                $output  Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
 *                                       a WP_Comment object, an associative array, or a numeric array, respectively. Default OBJECT.
lechuck's avatar
lechuck committed
191
 * @return WP_Comment|array|null Depends on $output value.
godog's avatar
godog committed
192
 */
lechuck's avatar
lechuck committed
193
194
195
196
function get_comment( &$comment = null, $output = OBJECT ) {
	if ( empty( $comment ) && isset( $GLOBALS['comment'] ) ) {
		$comment = $GLOBALS['comment'];
	}
godog's avatar
godog committed
197

lechuck's avatar
lechuck committed
198
	if ( $comment instanceof WP_Comment ) {
godog's avatar
godog committed
199
		$_comment = $comment;
lechuck's avatar
lechuck committed
200
201
	} elseif ( is_object( $comment ) ) {
		$_comment = new WP_Comment( $comment );
godog's avatar
godog committed
202
	} else {
lechuck's avatar
lechuck committed
203
204
205
206
207
		$_comment = WP_Comment::get_instance( $comment );
	}

	if ( ! $_comment ) {
		return null;
godog's avatar
godog committed
208
209
	}

lucha's avatar
lucha committed
210
211
212
213
214
215
216
217
	/**
	 * Fires after a comment is retrieved.
	 *
	 * @since 2.3.0
	 *
	 * @param mixed $_comment Comment data.
	 */
	$_comment = apply_filters( 'get_comment', $_comment );
godog's avatar
godog committed
218
219
220
221

	if ( $output == OBJECT ) {
		return $_comment;
	} elseif ( $output == ARRAY_A ) {
lechuck's avatar
lechuck committed
222
		return $_comment->to_array();
godog's avatar
godog committed
223
	} elseif ( $output == ARRAY_N ) {
lechuck's avatar
lechuck committed
224
		return array_values( $_comment->to_array() );
godog's avatar
godog committed
225
	}
lechuck's avatar
lechuck committed
226
	return $_comment;
godog's avatar
godog committed
227
228
229
230
231
232
233
234
235
}

/**
 * Retrieve a list of comments.
 *
 * The comment list can be for the blog as a whole or for an individual post.
 *
 * @since 2.7.0
 *
lucha's avatar
lucha committed
236
 * @param string|array $args Optional. Array or string of arguments. See WP_Comment_Query::__construct()
lechuck's avatar
lechuck committed
237
238
 *                           for information on accepted arguments. Default empty.
 * @return int|array List of comments or number of found comments if `$count` argument is true.
godog's avatar
godog committed
239
240
 */
function get_comments( $args = '' ) {
root's avatar
root committed
241
242
243
	$query = new WP_Comment_Query;
	return $query->query( $args );
}
godog's avatar
godog committed
244
245
246
247
248
249
250
251
252
253
254

/**
 * Retrieve all of the WordPress supported comment statuses.
 *
 * Comments have a limited set of valid status values, this provides the comment
 * status values and descriptions.
 *
 * @since 2.7.0
 *
 * @return array List of comment statuses.
 */
lechuck's avatar
lechuck committed
255
function get_comment_statuses() {
godog's avatar
godog committed
256
	$status = array(
samba's avatar
samba committed
257
258
259
260
		'hold'    => __( 'Unapproved' ),
		'approve' => _x( 'Approved', 'comment status' ),
		'spam'    => _x( 'Spam', 'comment status' ),
		'trash'   => _x( 'Trash', 'comment status' ),
godog's avatar
godog committed
261
262
263
264
265
	);

	return $status;
}

lechuck's avatar
lechuck committed
266
267
268
269
270
271
272
273
274
275
276
/**
 * Gets the default comment status for a post type.
 *
 * @since 4.3.0
 *
 * @param string $post_type    Optional. Post type. Default 'post'.
 * @param string $comment_type Optional. Comment type. Default 'comment'.
 * @return string Expected return value is 'open' or 'closed'.
 */
function get_default_comment_status( $post_type = 'post', $comment_type = 'comment' ) {
	switch ( $comment_type ) {
samba's avatar
samba committed
277
278
		case 'pingback':
		case 'trackback':
lechuck's avatar
lechuck committed
279
			$supports = 'trackbacks';
samba's avatar
samba committed
280
			$option   = 'ping';
lechuck's avatar
lechuck committed
281
			break;
samba's avatar
samba committed
282
		default:
lechuck's avatar
lechuck committed
283
			$supports = 'comments';
samba's avatar
samba committed
284
			$option   = 'comment';
lechuck's avatar
lechuck committed
285
286
287
288
289
290
291
292
293
294
295
296
	}

	// Set the status.
	if ( 'page' === $post_type ) {
		$status = 'closed';
	} elseif ( post_type_supports( $post_type, $supports ) ) {
		$status = get_option( "default_{$option}_status" );
	} else {
		$status = 'closed';
	}

	/**
lucha's avatar
lucha committed
297
	 * Filters the default comment status for the given post type.
lechuck's avatar
lechuck committed
298
299
300
301
302
303
304
305
	 *
	 * @since 4.3.0
	 *
	 * @param string $status       Default status for the given post type,
	 *                             either 'open' or 'closed'.
	 * @param string $post_type    Post type. Default is `post`.
	 * @param string $comment_type Type of comment. Default is `comment`.
	 */
samba's avatar
samba committed
306
	return apply_filters( 'get_default_comment_status', $status, $post_type, $comment_type );
lechuck's avatar
lechuck committed
307
308
}

godog's avatar
godog committed
309
310
311
312
/**
 * The date the last comment was modified.
 *
 * @since 1.5.0
lucha's avatar
lucha committed
313
314
 * @since 4.7.0 Replaced caching the modified date in a local static variable
 *              with the Object Cache API.
lechuck's avatar
lechuck committed
315
316
 *
 * @global wpdb $wpdb WordPress database abstraction object.
godog's avatar
godog committed
317
 *
lucha's avatar
lucha committed
318
319
 * @param string $timezone Which timezone to use in reference to 'gmt', 'blog', or 'server' locations.
 * @return string|false Last comment modified date on success, false on failure.
godog's avatar
godog committed
320
 */
lucha's avatar
lucha committed
321
function get_lastcommentmodified( $timezone = 'server' ) {
lechuck's avatar
lechuck committed
322
	global $wpdb;
godog's avatar
godog committed
323

lucha's avatar
lucha committed
324
	$timezone = strtolower( $timezone );
samba's avatar
samba committed
325
	$key      = "lastcommentmodified:$timezone";
godog's avatar
godog committed
326

lucha's avatar
lucha committed
327
328
329
330
	$comment_modified_date = wp_cache_get( $key, 'timeinfo' );
	if ( false !== $comment_modified_date ) {
		return $comment_modified_date;
	}
godog's avatar
godog committed
331

lucha's avatar
lucha committed
332
	switch ( $timezone ) {
godog's avatar
godog committed
333
		case 'gmt':
lucha's avatar
lucha committed
334
			$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" );
godog's avatar
godog committed
335
336
			break;
		case 'blog':
lucha's avatar
lucha committed
337
			$comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
godog's avatar
godog committed
338
339
			break;
		case 'server':
lucha's avatar
lucha committed
340
341
342
			$add_seconds_server = date( 'Z' );

			$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 ) );
godog's avatar
godog committed
343
344
345
			break;
	}

lucha's avatar
lucha committed
346
347
	if ( $comment_modified_date ) {
		wp_cache_set( $key, $comment_modified_date, 'timeinfo' );
godog's avatar
godog committed
348

lucha's avatar
lucha committed
349
350
351
352
		return $comment_modified_date;
	}

	return false;
godog's avatar
godog committed
353
354
355
356
357
}

/**
 * The amount of comments in a post or total comments.
 *
lucha's avatar
lucha committed
358
359
 * A lot like wp_count_comments(), in that they both return comment stats (albeit with different types).
 * The wp_count_comments() actually caches, but this function does not.
godog's avatar
godog committed
360
361
 *
 * @since 2.0.0
lechuck's avatar
lechuck committed
362
363
 *
 * @global wpdb $wpdb WordPress database abstraction object.
godog's avatar
godog committed
364
365
366
367
368
369
370
371
372
373
374
 *
 * @param int $post_id Optional. Comment amount in post if > 0, else total comments blog wide.
 * @return array The amount of spam, approved, awaiting moderation, and total comments.
 */
function get_comment_count( $post_id = 0 ) {
	global $wpdb;

	$post_id = (int) $post_id;

	$where = '';
	if ( $post_id > 0 ) {
samba's avatar
samba committed
375
		$where = $wpdb->prepare( 'WHERE comment_post_ID = %d', $post_id );
godog's avatar
godog committed
376
377
	}

samba's avatar
samba committed
378
379
	$totals = (array) $wpdb->get_results(
		"
godog's avatar
godog committed
380
381
382
383
		SELECT comment_approved, COUNT( * ) AS total
		FROM {$wpdb->comments}
		{$where}
		GROUP BY comment_approved
samba's avatar
samba committed
384
385
386
	",
		ARRAY_A
	);
godog's avatar
godog committed
387
388

	$comment_count = array(
lechuck's avatar
lechuck committed
389
390
391
392
393
394
395
		'approved'            => 0,
		'awaiting_moderation' => 0,
		'spam'                => 0,
		'trash'               => 0,
		'post-trashed'        => 0,
		'total_comments'      => 0,
		'all'                 => 0,
godog's avatar
godog committed
396
397
398
399
	);

	foreach ( $totals as $row ) {
		switch ( $row['comment_approved'] ) {
lechuck's avatar
lechuck committed
400
401
402
403
404
405
			case 'trash':
				$comment_count['trash'] = $row['total'];
				break;
			case 'post-trashed':
				$comment_count['post-trashed'] = $row['total'];
				break;
godog's avatar
godog committed
406
			case 'spam':
samba's avatar
samba committed
407
				$comment_count['spam']            = $row['total'];
lechuck's avatar
lechuck committed
408
				$comment_count['total_comments'] += $row['total'];
godog's avatar
godog committed
409
				break;
lechuck's avatar
lechuck committed
410
			case '1':
samba's avatar
samba committed
411
				$comment_count['approved']        = $row['total'];
godog's avatar
godog committed
412
				$comment_count['total_comments'] += $row['total'];
samba's avatar
samba committed
413
				$comment_count['all']            += $row['total'];
godog's avatar
godog committed
414
				break;
lechuck's avatar
lechuck committed
415
			case '0':
godog's avatar
godog committed
416
				$comment_count['awaiting_moderation'] = $row['total'];
samba's avatar
samba committed
417
418
				$comment_count['total_comments']     += $row['total'];
				$comment_count['all']                += $row['total'];
godog's avatar
godog committed
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
				break;
			default:
				break;
		}
	}

	return $comment_count;
}

//
// Comment meta functions
//

/**
 * Add meta data field to a comment.
 *
 * @since 2.9.0
ale's avatar
ale committed
436
 * @link https://codex.wordpress.org/Function_Reference/add_comment_meta
godog's avatar
godog committed
437
438
 *
 * @param int $comment_id Comment ID.
root's avatar
root committed
439
440
 * @param string $meta_key Metadata name.
 * @param mixed $meta_value Metadata value.
godog's avatar
godog committed
441
 * @param bool $unique Optional, default is false. Whether the same key should not be added.
lechuck's avatar
lechuck committed
442
 * @return int|bool Meta ID on success, false on failure.
godog's avatar
godog committed
443
 */
lucha's avatar
lucha committed
444
445
function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false ) {
	return add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique );
godog's avatar
godog committed
446
447
448
449
450
451
452
453
454
455
}

/**
 * Remove metadata matching criteria from a comment.
 *
 * You can match based on the key, or key and value. Removing based on key and
 * value, will keep from removing duplicate metadata with the same key. It also
 * allows removing all metadata matching key, if needed.
 *
 * @since 2.9.0
ale's avatar
ale committed
456
 * @link https://codex.wordpress.org/Function_Reference/delete_comment_meta
godog's avatar
godog committed
457
458
459
460
 *
 * @param int $comment_id comment ID
 * @param string $meta_key Metadata name.
 * @param mixed $meta_value Optional. Metadata value.
lechuck's avatar
lechuck committed
461
 * @return bool True on success, false on failure.
godog's avatar
godog committed
462
 */
lucha's avatar
lucha committed
463
464
function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
	return delete_metadata( 'comment', $comment_id, $meta_key, $meta_value );
godog's avatar
godog committed
465
466
467
468
469
470
}

/**
 * Retrieve comment meta field for a comment.
 *
 * @since 2.9.0
ale's avatar
ale committed
471
 * @link https://codex.wordpress.org/Function_Reference/get_comment_meta
godog's avatar
godog committed
472
473
 *
 * @param int $comment_id Comment ID.
lechuck's avatar
lechuck committed
474
 * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys.
godog's avatar
godog committed
475
476
477
478
 * @param bool $single Whether to return a single value.
 * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
 *  is true.
 */
lucha's avatar
lucha committed
479
480
function get_comment_meta( $comment_id, $key = '', $single = false ) {
	return get_metadata( 'comment', $comment_id, $key, $single );
godog's avatar
godog committed
481
482
483
484
485
486
487
488
489
490
491
}

/**
 * Update comment meta field based on comment ID.
 *
 * Use the $prev_value parameter to differentiate between meta fields with the
 * same key and comment ID.
 *
 * If the meta field for the comment does not exist, it will be added.
 *
 * @since 2.9.0
ale's avatar
ale committed
492
 * @link https://codex.wordpress.org/Function_Reference/update_comment_meta
godog's avatar
godog committed
493
494
 *
 * @param int $comment_id Comment ID.
root's avatar
root committed
495
496
 * @param string $meta_key Metadata key.
 * @param mixed $meta_value Metadata value.
godog's avatar
godog committed
497
 * @param mixed $prev_value Optional. Previous value to check before removing.
lechuck's avatar
lechuck committed
498
 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
godog's avatar
godog committed
499
 */
lucha's avatar
lucha committed
500
501
function update_comment_meta( $comment_id, $meta_key, $meta_value, $prev_value = '' ) {
	return update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
godog's avatar
godog committed
502
503
}

lechuck's avatar
lechuck committed
504
505
506
507
508
/**
 * Queues comments for metadata lazy-loading.
 *
 * @since 4.5.0
 *
samba's avatar
samba committed
509
 * @param WP_Comment[] $comments Array of comment objects.
lechuck's avatar
lechuck committed
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
 */
function wp_queue_comments_for_comment_meta_lazyload( $comments ) {
	// Don't use `wp_list_pluck()` to avoid by-reference manipulation.
	$comment_ids = array();
	if ( is_array( $comments ) ) {
		foreach ( $comments as $comment ) {
			if ( $comment instanceof WP_Comment ) {
				$comment_ids[] = $comment->comment_ID;
			}
		}
	}

	if ( $comment_ids ) {
		$lazyloader = wp_metadata_lazyloader();
		$lazyloader->queue_objects( 'comment', $comment_ids );
	}
}

lechuck's avatar
lechuck committed
528
529
530
531
532
/**
 * Sets the cookies used to store an unauthenticated commentator's identity. Typically used
 * to recall previous comments by this commentator that are still held in moderation.
 *
 * @since 3.4.0
kiki's avatar
kiki committed
533
534
535
536
537
 * @since 4.9.6 The `$cookies_consent` parameter was added.
 *
 * @param WP_Comment $comment         Comment object.
 * @param WP_User    $user            Comment author's user object. The user may not exist.
 * @param boolean    $cookies_consent Optional. Comment author's consent to store cookies. Default true.
lechuck's avatar
lechuck committed
538
 */
kiki's avatar
kiki committed
539
540
541
function wp_set_comment_cookies( $comment, $user, $cookies_consent = true ) {
	// If the user already exists, or the user opted out of cookies, don't set cookies.
	if ( $user->exists() ) {
lechuck's avatar
lechuck committed
542
		return;
kiki's avatar
kiki committed
543
544
545
546
547
548
549
550
551
552
553
	}

	if ( false === $cookies_consent ) {
		// Remove any existing cookies.
		$past = time() - YEAR_IN_SECONDS;
		setcookie( 'comment_author_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
		setcookie( 'comment_author_email_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
		setcookie( 'comment_author_url_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );

		return;
	}
lechuck's avatar
lechuck committed
554

lucha's avatar
lucha committed
555
	/**
lucha's avatar
lucha committed
556
	 * Filters the lifetime of the comment cookie in seconds.
lucha's avatar
lucha committed
557
558
559
560
561
	 *
	 * @since 2.8.0
	 *
	 * @param int $seconds Comment cookie lifetime. Default 30000000.
	 */
kiki's avatar
kiki committed
562
	$comment_cookie_lifetime = time() + apply_filters( 'comment_cookie_lifetime', 30000000 );
samba's avatar
samba committed
563
	$secure                  = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
kiki's avatar
kiki committed
564
565
566
	setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
	setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
	setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
lechuck's avatar
lechuck committed
567
568
}

godog's avatar
godog committed
569
570
571
572
573
574
575
576
577
/**
 * Sanitizes the cookies sent to the user already.
 *
 * Will only do anything if the cookies have already been created for the user.
 * Mostly used after cookies had been sent to use elsewhere.
 *
 * @since 2.0.4
 */
function sanitize_comment_cookies() {
samba's avatar
samba committed
578
	if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
lucha's avatar
lucha committed
579
		/**
lucha's avatar
lucha committed
580
		 * Filters the comment author's name cookie before it is set.
lucha's avatar
lucha committed
581
582
583
584
585
586
587
588
		 *
		 * When this filter hook is evaluated in wp_filter_comment(),
		 * the comment author's name string is passed.
		 *
		 * @since 1.5.0
		 *
		 * @param string $author_cookie The comment author name cookie.
		 */
samba's avatar
samba committed
589
590
591
592
		$comment_author                            = apply_filters( 'pre_comment_author_name', $_COOKIE[ 'comment_author_' . COOKIEHASH ] );
		$comment_author                            = wp_unslash( $comment_author );
		$comment_author                            = esc_attr( $comment_author );
		$_COOKIE[ 'comment_author_' . COOKIEHASH ] = $comment_author;
godog's avatar
godog committed
593
594
	}

samba's avatar
samba committed
595
	if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
lucha's avatar
lucha committed
596
		/**
lucha's avatar
lucha committed
597
		 * Filters the comment author's email cookie before it is set.
lucha's avatar
lucha committed
598
599
600
601
602
603
604
605
		 *
		 * When this filter hook is evaluated in wp_filter_comment(),
		 * the comment author's email string is passed.
		 *
		 * @since 1.5.0
		 *
		 * @param string $author_email_cookie The comment author email cookie.
		 */
samba's avatar
samba committed
606
607
608
609
		$comment_author_email                            = apply_filters( 'pre_comment_author_email', $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] );
		$comment_author_email                            = wp_unslash( $comment_author_email );
		$comment_author_email                            = esc_attr( $comment_author_email );
		$_COOKIE[ 'comment_author_email_' . COOKIEHASH ] = $comment_author_email;
godog's avatar
godog committed
610
611
	}

samba's avatar
samba committed
612
	if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
lucha's avatar
lucha committed
613
		/**
lucha's avatar
lucha committed
614
		 * Filters the comment author's URL cookie before it is set.
lucha's avatar
lucha committed
615
616
617
618
619
620
621
622
		 *
		 * When this filter hook is evaluated in wp_filter_comment(),
		 * the comment author's URL string is passed.
		 *
		 * @since 1.5.0
		 *
		 * @param string $author_url_cookie The comment author URL cookie.
		 */
samba's avatar
samba committed
623
624
625
		$comment_author_url                            = apply_filters( 'pre_comment_author_url', $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] );
		$comment_author_url                            = wp_unslash( $comment_author_url );
		$_COOKIE[ 'comment_author_url_' . COOKIEHASH ] = $comment_author_url;
godog's avatar
godog committed
626
627
628
629
630
631
632
	}
}

/**
 * Validates whether this comment is allowed to be made.
 *
 * @since 2.0.0
lucha's avatar
lucha committed
633
634
 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function to
 *              return a WP_Error object instead of dying.
lechuck's avatar
lechuck committed
635
636
 *
 * @global wpdb $wpdb WordPress database abstraction object.
godog's avatar
godog committed
637
 *
lucha's avatar
lucha committed
638
639
640
641
642
643
 * @param array $commentdata Contains information on the comment.
 * @param bool  $avoid_die   When true, a disallowed comment will result in the function
 *                           returning a WP_Error object, rather than executing wp_die().
 *                           Default false.
 * @return int|string|WP_Error Allowed comments return the approval status (0|1|'spam').
 *                             If `$avoid_die` is true, disallowed comments return a WP_Error.
godog's avatar
godog committed
644
 */
lucha's avatar
lucha committed
645
function wp_allow_comment( $commentdata, $avoid_die = false ) {
godog's avatar
godog committed
646
647
648
649
	global $wpdb;

	// Simple duplicate check
	// expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
lucha's avatar
lucha committed
650
651
652
653
654
655
656
657
	$dupe = $wpdb->prepare(
		"SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = %s AND comment_approved != 'trash' AND ( comment_author = %s ",
		wp_unslash( $commentdata['comment_post_ID'] ),
		wp_unslash( $commentdata['comment_parent'] ),
		wp_unslash( $commentdata['comment_author'] )
	);
	if ( $commentdata['comment_author_email'] ) {
		$dupe .= $wpdb->prepare(
samba's avatar
samba committed
658
			'AND comment_author_email = %s ',
lucha's avatar
lucha committed
659
660
661
662
			wp_unslash( $commentdata['comment_author_email'] )
		);
	}
	$dupe .= $wpdb->prepare(
samba's avatar
samba committed
663
		') AND comment_content = %s LIMIT 1',
lucha's avatar
lucha committed
664
665
		wp_unslash( $commentdata['comment_content'] )
	);
lechuck's avatar
lechuck committed
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681

	$dupe_id = $wpdb->get_var( $dupe );

	/**
	 * Filters the ID, if any, of the duplicate comment found when creating a new comment.
	 *
	 * Return an empty value from this filter to allow what WP considers a duplicate comment.
	 *
	 * @since 4.4.0
	 *
	 * @param int   $dupe_id     ID of the comment identified as a duplicate.
	 * @param array $commentdata Data for the comment being created.
	 */
	$dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata );

	if ( $dupe_id ) {
lucha's avatar
lucha committed
682
683
684
685
686
687
688
		/**
		 * Fires immediately after a duplicate comment is detected.
		 *
		 * @since 3.0.0
		 *
		 * @param array $commentdata Comment data.
		 */
godog's avatar
godog committed
689
		do_action( 'comment_duplicate_trigger', $commentdata );
agata's avatar
agata committed
690
691
692
693
694
695
696
697
698
699

		/**
		 * Filters duplicate comment error message.
		 *
		 * @since 5.2.0
		 *
		 * @param string $comment_duplicate_message Duplicate comment error message.
		 */
		$comment_duplicate_message = apply_filters( 'comment_duplicate_message', __( 'Duplicate comment detected; it looks as though you&#8217;ve already said that!' ) );

lucha's avatar
lucha committed
700
		if ( true === $avoid_die ) {
agata's avatar
agata committed
701
			return new WP_Error( 'comment_duplicate', $comment_duplicate_message, 409 );
lucha's avatar
lucha committed
702
703
		} else {
			if ( wp_doing_ajax() ) {
agata's avatar
agata committed
704
				die( $comment_duplicate_message );
lucha's avatar
lucha committed
705
706
			}

agata's avatar
agata committed
707
			wp_die( $comment_duplicate_message, 409 );
lucha's avatar
lucha committed
708
		}
godog's avatar
godog committed
709
710
	}

lucha's avatar
lucha committed
711
712
713
714
715
716
	/**
	 * Fires immediately before a comment is marked approved.
	 *
	 * Allows checking for comment flooding.
	 *
	 * @since 2.3.0
lucha's avatar
lucha committed
717
	 * @since 4.7.0 The `$avoid_die` parameter was added.
lucha's avatar
lucha committed
718
719
720
721
	 *
	 * @param string $comment_author_IP    Comment author's IP address.
	 * @param string $comment_author_email Comment author's email.
	 * @param string $comment_date_gmt     GMT date the comment was posted.
lucha's avatar
lucha committed
722
723
	 * @param bool   $avoid_die            Whether to prevent executing wp_die()
	 *                                     or die() if a comment flood is occurring.
lucha's avatar
lucha committed
724
	 */
lucha's avatar
lucha committed
725
726
727
728
	do_action(
		'check_comment_flood',
		$commentdata['comment_author_IP'],
		$commentdata['comment_author_email'],
lucha's avatar
lucha committed
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
		$commentdata['comment_date_gmt'],
		$avoid_die
	);

	/**
	 * Filters whether a comment is part of a comment flood.
	 *
	 * The default check is wp_check_comment_flood(). See check_comment_flood_db().
	 *
	 * @since 4.7.0
	 *
	 * @param bool   $is_flood             Is a comment flooding occurring? Default false.
	 * @param string $comment_author_IP    Comment author's IP address.
	 * @param string $comment_author_email Comment author's email.
	 * @param string $comment_date_gmt     GMT date the comment was posted.
	 * @param bool   $avoid_die            Whether to prevent executing wp_die()
	 *                                     or die() if a comment flood is occurring.
	 */
	$is_flood = apply_filters(
		'wp_is_comment_flood',
		false,
		$commentdata['comment_author_IP'],
		$commentdata['comment_author_email'],
		$commentdata['comment_date_gmt'],
		$avoid_die
lucha's avatar
lucha committed
754
	);
godog's avatar
godog committed
755

lucha's avatar
lucha committed
756
	if ( $is_flood ) {
agata's avatar
agata committed
757
758
759
760
		/** This filter is documented in wp-includes/comment-template.php */
		$comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );

		return new WP_Error( 'comment_flood', $comment_flood_message, 429 );
lucha's avatar
lucha committed
761
762
	}

lucha's avatar
lucha committed
763
	if ( ! empty( $commentdata['user_id'] ) ) {
samba's avatar
samba committed
764
765
766
767
768
769
770
		$user        = get_userdata( $commentdata['user_id'] );
		$post_author = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
				$commentdata['comment_post_ID']
			)
		);
godog's avatar
godog committed
771
772
	}

lucha's avatar
lucha committed
773
	if ( isset( $user ) && ( $commentdata['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
godog's avatar
godog committed
774
775
		// The author and the admins get respect.
		$approved = 1;
lucha's avatar
lucha committed
776
	} else {
godog's avatar
godog committed
777
		// Everyone else's comments will be checked.
lucha's avatar
lucha committed
778
779
780
781
782
783
784
785
786
		if ( check_comment(
			$commentdata['comment_author'],
			$commentdata['comment_author_email'],
			$commentdata['comment_author_url'],
			$commentdata['comment_content'],
			$commentdata['comment_author_IP'],
			$commentdata['comment_agent'],
			$commentdata['comment_type']
		) ) {
godog's avatar
godog committed
787
			$approved = 1;
lucha's avatar
lucha committed
788
		} else {
godog's avatar
godog committed
789
			$approved = 0;
lucha's avatar
lucha committed
790
791
792
793
794
795
796
797
798
799
		}

		if ( wp_blacklist_check(
			$commentdata['comment_author'],
			$commentdata['comment_author_email'],
			$commentdata['comment_author_url'],
			$commentdata['comment_content'],
			$commentdata['comment_author_IP'],
			$commentdata['comment_agent']
		) ) {
lechuck's avatar
lechuck committed
800
			$approved = EMPTY_TRASH_DAYS ? 'trash' : 'spam';
lucha's avatar
lucha committed
801
		}
godog's avatar
godog committed
802
803
	}

lucha's avatar
lucha committed
804
	/**
lucha's avatar
lucha committed
805
	 * Filters a comment's approval status before it is set.
lucha's avatar
lucha committed
806
807
	 *
	 * @since 2.1.0
lucha's avatar
lucha committed
808
809
	 * @since 4.9.0 Returning a WP_Error value from the filter will shortcircuit comment insertion and
	 *              allow skipping further processing.
lucha's avatar
lucha committed
810
	 *
lucha's avatar
lucha committed
811
812
	 * @param bool|string|WP_Error $approved    The approval status. Accepts 1, 0, 'spam' or WP_Error.
	 * @param array                $commentdata Comment data.
lucha's avatar
lucha committed
813
	 */
root's avatar
root committed
814
	$approved = apply_filters( 'pre_comment_approved', $approved, $commentdata );
godog's avatar
godog committed
815
816
817
818
	return $approved;
}

/**
lucha's avatar
lucha committed
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
 * Hooks WP's native database-based comment-flood check.
 *
 * This wrapper maintains backward compatibility with plugins that expect to
 * be able to unhook the legacy check_comment_flood_db() function from
 * 'check_comment_flood' using remove_action().
 *
 * @since 2.3.0
 * @since 4.7.0 Converted to be an add_filter() wrapper.
 */
function check_comment_flood_db() {
	add_filter( 'wp_is_comment_flood', 'wp_check_comment_flood', 10, 5 );
}

/**
 * Checks whether comment flooding is occurring.
godog's avatar
godog committed
834
835
836
837
 *
 * Won't run, if current user can manage options, so to not block
 * administrators.
 *
lucha's avatar
lucha committed
838
 * @since 4.7.0
lechuck's avatar
lechuck committed
839
840
 *
 * @global wpdb $wpdb WordPress database abstraction object.
godog's avatar
godog committed
841
 *
lucha's avatar
lucha committed
842
 * @param bool   $is_flood  Is a comment flooding occurring?
lucha's avatar
lucha committed
843
844
 * @param string $ip        Comment author's IP address.
 * @param string $email     Comment author's email address.
lucha's avatar
lucha committed
845
846
847
848
849
 * @param string $date      MySQL time string.
 * @param bool   $avoid_die When true, a disallowed comment will result in the function
 *                          returning a WP_Error object, rather than executing wp_die().
 *                          Default false.
 * @return bool Whether comment flooding is occurring.
godog's avatar
godog committed
850
 */
lucha's avatar
lucha committed
851
852
function wp_check_comment_flood( $is_flood, $ip, $email, $date, $avoid_die = false ) {

godog's avatar
godog committed
853
	global $wpdb;
lucha's avatar
lucha committed
854
855
856
857
858
859

	// Another callback has declared a flood. Trust it.
	if ( true === $is_flood ) {
		return $is_flood;
	}

lechuck's avatar
lechuck committed
860
861
	// don't throttle admins or moderators
	if ( current_user_can( 'manage_options' ) || current_user_can( 'moderate_comments' ) ) {
lucha's avatar
lucha committed
862
		return false;
lechuck's avatar
lechuck committed
863
	}
lechuck's avatar
lechuck committed
864
	$hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
lechuck's avatar
lechuck committed
865
866

	if ( is_user_logged_in() ) {
samba's avatar
samba committed
867
		$user         = get_current_user_id();
lechuck's avatar
lechuck committed
868
869
		$check_column = '`user_id`';
	} else {
samba's avatar
samba committed
870
		$user         = $ip;
lechuck's avatar
lechuck committed
871
872
873
		$check_column = '`comment_author_IP`';
	}

samba's avatar
samba committed
874
	$sql      = $wpdb->prepare(
lechuck's avatar
lechuck committed
875
876
877
878
879
880
881
		"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",
		$hour_ago,
		$user,
		$email
	);
	$lasttime = $wpdb->get_var( $sql );
	if ( $lasttime ) {
samba's avatar
samba committed
882
883
		$time_lastcomment = mysql2date( 'U', $lasttime, false );
		$time_newcomment  = mysql2date( 'U', $date, false );
lucha's avatar
lucha committed
884
		/**
lucha's avatar
lucha committed
885
		 * Filters the comment flood status.
lucha's avatar
lucha committed
886
887
888
889
890
891
892
893
		 *
		 * @since 2.1.0
		 *
		 * @param bool $bool             Whether a comment flood is occurring. Default false.
		 * @param int  $time_lastcomment Timestamp of when the last comment was posted.
		 * @param int  $time_newcomment  Timestamp of when the new comment was posted.
		 */
		$flood_die = apply_filters( 'comment_flood_filter', false, $time_lastcomment, $time_newcomment );
godog's avatar
godog committed
894
		if ( $flood_die ) {
lucha's avatar
lucha committed
895
896
897
898
899
900
901
902
903
			/**
			 * Fires before the comment flood message is triggered.
			 *
			 * @since 1.5.0
			 *
			 * @param int $time_lastcomment Timestamp of when the last comment was posted.
			 * @param int $time_newcomment  Timestamp of when the new comment was posted.
			 */
			do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
agata's avatar
agata committed
904

lucha's avatar
lucha committed
905
906
907
			if ( true === $avoid_die ) {
				return true;
			} else {
agata's avatar
agata committed
908
909
910
911
912
913
914
915
916
				/**
				 * Filters the comment flood error message.
				 *
				 * @since 5.2.0
				 *
				 * @param string $comment_flood_message Comment flood error message.
				 */
				$comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );

lucha's avatar
lucha committed
917
				if ( wp_doing_ajax() ) {
agata's avatar
agata committed
918
					die( $comment_flood_message );
lucha's avatar
lucha committed
919
				}
godog's avatar
godog committed
920

agata's avatar
agata committed
921
				wp_die( $comment_flood_message, 429 );
lucha's avatar
lucha committed
922
			}
godog's avatar
godog committed
923
924
		}
	}
lucha's avatar
lucha committed
925
926

	return false;
godog's avatar
godog committed
927
928
929
930
931
932
933
}

/**
 * Separates an array of comments into an array keyed by comment_type.
 *
 * @since 2.7.0
 *
samba's avatar
samba committed
934
935
 * @param WP_Comment[] $comments Array of comments
 * @return WP_Comment[] Array of comments keyed by comment_type.
godog's avatar
godog committed
936
 */
samba's avatar
samba committed
937
938
939
940
941
942
943
944
function separate_comments( &$comments ) {
	$comments_by_type = array(
		'comment'   => array(),
		'trackback' => array(),
		'pingback'  => array(),
		'pings'     => array(),
	);
	$count            = count( $comments );
godog's avatar
godog committed
945
	for ( $i = 0; $i < $count; $i++ ) {
samba's avatar
samba committed
946
947
		$type = $comments[ $i ]->comment_type;
		if ( empty( $type ) ) {
godog's avatar
godog committed
948
			$type = 'comment';
samba's avatar
samba committed
949
950
951
952
953
		}
		$comments_by_type[ $type ][] = &$comments[ $i ];
		if ( 'trackback' == $type || 'pingback' == $type ) {
			$comments_by_type['pings'][] = &$comments[ $i ];
		}
godog's avatar
godog committed
954
955
956
957
958
959
960
961
962
	}

	return $comments_by_type;
}

/**
 * Calculate the total number of comment pages.
 *
 * @since 2.7.0
lechuck's avatar
lechuck committed
963
 *
godog's avatar
godog committed
964
965
 * @uses Walker_Comment
 *
lechuck's avatar
lechuck committed
966
967
 * @global WP_Query $wp_query
 *
samba's avatar
samba committed
968
969
970
 * @param WP_Comment[] $comments Optional. Array of WP_Comment objects. Defaults to $wp_query->comments.
 * @param int          $per_page Optional. Comments per page.
 * @param bool         $threaded Optional. Control over flat or threaded comments.
godog's avatar
godog committed
971
972
973
974
975
 * @return int Number of comment pages.
 */
function get_comment_pages_count( $comments = null, $per_page = null, $threaded = null ) {
	global $wp_query;

samba's avatar
samba committed
976
	if ( null === $comments && null === $per_page && null === $threaded && ! empty( $wp_query->max_num_comment_pages ) ) {
godog's avatar
godog committed
977
		return $wp_query->max_num_comment_pages;
samba's avatar
samba committed
978
	}
godog's avatar
godog committed
979

samba's avatar
samba committed
980
	if ( ( ! $comments || ! is_array( $comments ) ) && ! empty( $wp_query->comments ) ) {
godog's avatar
godog committed
981
		$comments = $wp_query->comments;
samba's avatar
samba committed
982
	}
godog's avatar
godog committed
983

samba's avatar
samba committed
984
	if ( empty( $comments ) ) {
godog's avatar
godog committed
985
		return 0;
samba's avatar
samba committed
986
	}
godog's avatar
godog committed
987

lechuck's avatar
lechuck committed
988
	if ( ! get_option( 'page_comments' ) ) {
lucha's avatar
lucha committed
989
		return 1;
lechuck's avatar
lechuck committed
990
	}
lucha's avatar
lucha committed
991

samba's avatar
samba committed
992
993
994
995
996
997
998
	if ( ! isset( $per_page ) ) {
		$per_page = (int) get_query_var( 'comments_per_page' );
	}
	if ( 0 === $per_page ) {
		$per_page = (int) get_option( 'comments_per_page' );
	}
	if ( 0 === $per_page ) {
godog's avatar
godog committed
999
		return 1;
samba's avatar
samba committed
1000
	}
godog's avatar
godog committed
1001

samba's avatar
samba committed
1002
1003
1004
	if ( ! isset( $threaded ) ) {
		$threaded = get_option( 'thread_comments' );
	}
godog's avatar
godog committed
1005
1006
1007

	if ( $threaded ) {
		$walker = new Walker_Comment;
samba's avatar
samba committed
1008
		$count  = ceil( $walker->get_number_of_root_elements( $comments ) / $per_page );
godog's avatar
godog committed
1009
1010
1011
1012
	} else {
		$count = ceil( count( $comments ) / $per_page );
	}

lechuck's avatar
lechuck committed
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
	return $count;
}

/**
 * Calculate what page number a comment will appear on for comment paging.
 *
 * @since 2.7.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int   $comment_ID Comment ID.
 * @param array $args {
 *      Array of optional arguments.
 *      @type string     $type      Limit paginated comments to those matching a given type. Accepts 'comment',
 *                                  'trackback', 'pingback', 'pings' (trackbacks and pingbacks), or 'all'.
 *                                  Default is 'all'.
 *      @type int        $per_page  Per-page count to use when calculating pagination. Defaults to the value of the
 *                                  'comments_per_page' option.
 *      @type int|string $max_depth If greater than 1, comment page will be determined for the top-level parent of
 *                                  `$comment_ID`. Defaults to the value of the 'thread_comments_depth' option.
 * } *
 * @return int|null Comment page number or null on error.
 */
function get_page_of_comment( $comment_ID, $args = array() ) {
	global $wpdb;

	$page = null;

samba's avatar
samba committed
1041
	if ( ! $comment = get_comment( $comment_ID ) ) {
lechuck's avatar
lechuck committed
1042
		return;
samba's avatar
samba committed
1043
	}
lechuck's avatar
lechuck committed
1044

samba's avatar
samba committed
1045
1046
1047
1048
1049
1050
1051
	$defaults      = array(
		'type'      => 'all',
		'page'      => '',
		'per_page'  => '',
		'max_depth' => '',
	);
	$args          = wp_parse_args( $args, $defaults );
lechuck's avatar
lechuck committed
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
	$original_args = $args;

	// Order of precedence: 1. `$args['per_page']`, 2. 'comments_per_page' query_var, 3. 'comments_per_page' option.
	if ( get_option( 'page_comments' ) ) {
		if ( '' === $args['per_page'] ) {
			$args['per_page'] = get_query_var( 'comments_per_page' );
		}

		if ( '' === $args['per_page'] ) {
			$args['per_page'] = get_option( 'comments_per_page' );
		}
	}

samba's avatar
samba committed
1065
	if ( empty( $args['per_page'] ) ) {
lechuck's avatar
lechuck committed
1066
		$args['per_page'] = 0;
samba's avatar
samba committed
1067
		$args['page']     = 0;
lechuck's avatar
lechuck committed
1068
1069
1070
1071
1072
1073
1074
1075
	}

	if ( $args['per_page'] < 1 ) {
		$page = 1;
	}

	if ( null === $page ) {
		if ( '' === $args['max_depth'] ) {
samba's avatar
samba committed
1076
1077
1078
			if ( get_option( 'thread_comments' ) ) {
				$args['max_depth'] = get_option( 'thread_comments_depth' );
			} else {
lechuck's avatar
lechuck committed
1079
				$args['max_depth'] = -1;
samba's avatar
samba committed
1080
			}
lechuck's avatar
lechuck committed
1081
1082
1083
		}

		// Find this comment's top level parent if threading is enabled
samba's avatar
samba committed
1084
		if ( $args['max_depth'] > 1 && 0 != $comment->comment_parent ) {
lechuck's avatar
lechuck committed
1085
			return get_page_of_comment( $comment->comment_parent, $args );
samba's avatar
samba committed
1086
		}
lechuck's avatar
lechuck committed
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097

		$comment_args = array(
			'type'       => $args['type'],
			'post_id'    => $comment->comment_post_ID,
			'fields'     => 'ids',
			'count'      => true,
			'status'     => 'approve',
			'parent'     => 0,
			'date_query' => array(
				array(
					'column' => "$wpdb->comments.comment_date_gmt",
lucha's avatar
lucha committed
1098
					'before' => $comment->comment_date_gmt,
samba's avatar
samba committed
1099
				),
lechuck's avatar
lechuck committed
1100
1101
1102
			),
		);

samba's avatar
samba committed
1103
		$comment_query       = new WP_Comment_Query();
lechuck's avatar
lechuck committed
1104
1105
1106
1107
1108
1109
		$older_comment_count = $comment_query->query( $comment_args );

		// No older comments? Then it's page #1.
		if ( 0 == $older_comment_count ) {
			$page = 1;

samba's avatar
samba committed
1110
			// Divide comments older than this one by comments per page to get this comment's page number
lechuck's avatar
lechuck committed
1111
1112
1113
1114
1115
1116
1117
1118
1119
		} else {
			$page = ceil( ( $older_comment_count + 1 ) / $args['per_page'] );
		}
	}

	/**
	 * Filters the calculated page on which a comment appears.
	 *
	 * @since 4.4.0
lucha's avatar
lucha committed
1120
	 * @since 4.7.0 Introduced the `$comment_ID` parameter.
lechuck's avatar
lechuck committed
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
	 *
	 * @param int   $page          Comment page.
	 * @param array $args {
	 *     Arguments used to calculate pagination. These include arguments auto-detected by the function,
	 *     based on query vars, system settings, etc. For pristine arguments passed to the function,
	 *     see `$original_args`.
	 *
	 *     @type string $type      Type of comments to count.
	 *     @type int    $page      Calculated current page.
	 *     @type int    $per_page  Calculated number of comments per page.
	 *     @type int    $max_depth Maximum comment threading depth allowed.
	 * }
	 * @param array $original_args {
	 *     Array of arguments passed to the function. Some or all of these may not be set.
	 *
	 *     @type string $type      Type of comments to count.
	 *     @type int    $page      Current comment page.
	 *     @type int    $per_page  Number of comments per page.
	 *     @type int    $max_depth Maximum comment threading depth allowed.
	 * }
lucha's avatar
lucha committed
1141
	 * @param int $comment_ID ID of the comment.
lechuck's avatar
lechuck committed
1142
	 */
lucha's avatar
lucha committed
1143
	return apply_filters( 'get_page_of_comment', (int) $page, $args, $original_args, $comment_ID );
godog's avatar
godog committed
1144
1145
1146
}

/**
lechuck's avatar
lechuck committed
1147
 * Retrieves the maximum character lengths for the comment form fields.
godog's avatar
godog committed
1148
 *
lechuck's avatar
lechuck committed
1149
 * @since 4.5.0
godog's avatar
godog committed
1150
 *
lechuck's avatar
lechuck committed
1151
 * @global wpdb $wpdb WordPress database abstraction object.
lechuck's avatar
lechuck committed
1152
 *
lechuck's avatar
lechuck committed
1153
 * @return array Maximum character length for the comment form fields.
godog's avatar
godog committed
1154
 */
lechuck's avatar
lechuck committed
1155
function wp_get_comment_fields_max_lengths() {
godog's avatar
godog committed
1156
1157
	global $wpdb;

lechuck's avatar
lechuck committed
1158
1159
1160
1161
1162
1163
	$lengths = array(
		'comment_author'       => 245,
		'comment_author_email' => 100,
		'comment_author_url'   => 200,
		'comment_content'      => 65525,
	);
godog's avatar
godog committed
1164

lechuck's avatar
lechuck committed
1165
1166
1167
1168
	if ( $wpdb->is_mysql ) {
		foreach ( $lengths as $column => $length ) {
			$col_length = $wpdb->get_col_length( $wpdb->comments, $column );
			$max_length = 0;
godog's avatar
godog committed
1169

lechuck's avatar
lechuck committed
1170
1171
1172
1173
			// No point if we can't get the DB column lengths
			if ( is_wp_error( $col_length ) ) {
				break;
			}
godog's avatar
godog committed
1174

lechuck's avatar
lechuck committed
1175
1176
1177
1178
			if ( ! is_array( $col_length ) && (int) $col_length > 0 ) {
				$max_length = (int) $col_length;
			} elseif ( is_array( $col_length ) && isset( $col_length['length'] ) && intval( $col_length['length'] ) > 0 ) {
				$max_length = (int) $col_length['length'];
godog's avatar
godog committed
1179

lechuck's avatar
lechuck committed
1180
1181
1182
1183
				if ( ! empty( $col_length['type'] ) && 'byte' === $col_length['type'] ) {
					$max_length = $max_length - 10;
				}
			}
godog's avatar
godog committed
1184

lechuck's avatar
lechuck committed
1185
1186
1187
1188
1189
			if ( $max_length > 0 ) {
				$lengths[ $column ] = $max_length;
			}
		}
	}
godog's avatar
godog committed
1190

lechuck's avatar
lechuck committed
1191
1192
1193
1194
1195
1196
1197
1198
	/**
	 * Filters the lengths for the comment form fields.
	 *
	 * @since 4.5.0
	 *
	 * @param array $lengths Associative array `'field_name' => 'maximum length'`.
	 */
	return apply_filters( 'wp_get_comment_fields_max_lengths', $lengths );
godog's avatar
godog committed
1199
1200
}

lucha's avatar
lucha committed
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
/**
 * Compares the lengths of comment data against the maximum character limits.
 *
 * @since 4.7.0
 *
 * @param array $comment_data Array of arguments for inserting a comment.
 * @return WP_Error|true WP_Error when a comment field exceeds the limit,
 *                       otherwise true.
 */
function wp_check_comment_data_max_lengths( $comment_data ) {
	$max_lengths = wp_get_comment_fields_max_lengths();

	if ( isset( $comment_data['comment_author'] ) && mb_strlen( $comment_data['comment_author'], '8bit' ) > $max_lengths['comment_author'] ) {
		return new WP_Error( 'comment_author_column_length', __( '<strong>ERROR</strong>: your name is too long.' ), 200 );
	}

	if ( isset( $comment_data['comment_author_email'] ) && strlen( $comment_data['comment_author_email'] ) > $max_lengths['comment_author_email'] ) {
		return new WP_Error( 'comment_author_email_column_length', __( '<strong>ERROR</strong>: your email address is too long.' ), 200 );
	}

	if ( isset( $comment_data['comment_author_url'] ) && strlen( $comment_data['comment_author_url'] ) > $max_lengths['comment_author_url'] ) {
		return new WP_Error( 'comment_author_url_column_length', __( '<strong>ERROR</strong>: your url is too long.' ), 200 );
	}

	if ( isset( $comment_data['comment_content'] ) && mb_strlen( $comment_data['comment_content'], '8bit' ) > $max_lengths['comment_content'] ) {
		return new WP_Error( 'comment_content_column_length', __( '<strong>ERROR</strong>: your comment is too long.' ), 200 );
	}

	return true;
}

godog's avatar
godog committed
1232
1233
1234
1235
1236
1237
1238
1239
1240
/**
 * Does comment contain blacklisted characters or words.
 *
 * @since 1.5.0
 *
 * @param string $author The author of the comment
 * @param string $email The email of the comment
 * @param string $url The url used in the comment
 * @param string $comment The comment content
lucha's avatar
lucha committed
1241
 * @param string $user_ip The comment author's IP address
godog's avatar
godog committed
1242
1243
1244
 * @param string $user_agent The author's browser user agent
 * @return bool True if comment contains blacklisted content, false if comment does not
 */
samba's avatar
samba committed
1245
function wp_blacklist_check( $author, $email, $url, $comment, $user_ip, $user_agent ) {
lucha's avatar
lucha committed
1246
1247
1248
1249
1250