Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/comment.php (30 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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
	/** This filter is documented in wp-includes/comment-template.php */
47
	$comment = apply_filters( 'comment_text', $comment, null, array() );
48
49
	// Check for the number of external links if a max allowed number is set.
50
	if ( $max_links = get_option( 'comment_max_links' ) ) {
51
		$num_links = preg_match_all( '/<a [^>]*href/i', $comment, $out );
52
53
		/**
54
		 * Filters the number of links found in a comment.
55
		 *
56
		 * @since 3.0.0
57
		 * @since 4.7.0 Added the `$comment` parameter.
58
		 *
59
		 * @param int    $num_links The number of links found.
60
		 * @param string $url       Comment author's URL. Included in allowed links total.
61
		 * @param string $comment   Content of the comment.
62
		 */
63
		$num_links = apply_filters( 'comment_max_links_url', $num_links, $url, $comment );
64
65
		/*
66
		 * If the number of links in the comment exceeds the allowed amount,
67
		 * fail the check by returning false.
68
		 */
69
		if ( $num_links >= $max_links )
70
			return false;
71
	}
72
73
	$mod_keys = trim(get_option('moderation_keys'));
74
75
	// If moderation 'keys' (keywords) are set, process them.
76
	if ( !empty($mod_keys) ) {
77
		$words = explode("\n", $mod_keys );
78
79
		foreach ( (array) $words as $word) {
80
			$word = trim($word);
81
82
			// Skip empty lines.
83
			if ( empty($word) )
84
				continue;
85
86
			/*
87
			 * Do some escaping magic so that '#' (number of) characters in the spam
88
			 * words don't break things:
89
			 */
90
			$word = preg_quote($word, '#');
91
92
			/*
93
			 * Check the comment fields for moderation keywords. If any are found,
94
			 * fail the check for the given field by returning false.
95
			 */
96
			$pattern = "#$word#i";
97
			if ( preg_match($pattern, $author) ) return false;
98
			if ( preg_match($pattern, $email) ) return false;
99
			if ( preg_match($pattern, $url) ) return false;
100
			if ( preg_match($pattern, $comment) ) return false;
101
			if ( preg_match($pattern, $user_ip) ) return false;
102
			if ( preg_match($pattern, $user_agent) ) return false;
103
		}
104
	}
105
106
	/*
107
	 * Check if the option to approve comments by previously-approved authors is enabled.
108
	 *
109
	 * If it is enabled, check whether the comment author has a previously-approved comment,
110
	 * as well as whether there are any moderation keywords (if set) present in the author
111
	 * email address. If both checks pass, return true. Otherwise, return false.
112
	 */
113
	if ( 1 == get_option('comment_whitelist')) {
114
		if ( 'trackback' != $comment_type && 'pingback' != $comment_type && $author != '' && $email != '' ) {
115
			$comment_user = get_user_by( 'email', wp_unslash( $email ) );
0 ignored issues
show
It seems like wp_unslash($email) targeting wp_unslash() can also be of type array; however, get_user_by() does only seem to accept integer|string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
116
			if ( ! empty( $comment_user->ID ) ) {
117
				$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 ) );
118
			} else {
119
				// expected_slashed ($author, $email)
120
				$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 ) );
121
			}
122
			if ( ( 1 == $ok_to_comment ) &&
0 ignored issues
show
The if-else statement can be simplified to return 1 == $ok_to_comme...os($email, $mod_keys));.
Loading history...
123
				( empty($mod_keys) || false === strpos( $email, $mod_keys) ) )
124
					return true;
125
			else
126
				return false;
127
		} else {
128
			return false;
129
		}
130
	}
131
	return true;
132
}
133
134
/**
135
 * Retrieve the approved comments for post $post_id.
136
 *
137
 * @since 2.0.0
138
 * @since 4.1.0 Refactored to leverage WP_Comment_Query over a direct query.
139
 *
140
 * @param  int   $post_id The ID of the post.
141
 * @param  array $args    Optional. See WP_Comment_Query::query() for information on accepted arguments.
142
 * @return int|array $comments The approved comments, or number of comments if `$count`
143
 *                             argument is true.
144
 */
145
function get_approved_comments( $post_id, $args = array() ) {
146
	if ( ! $post_id ) {
147
		return array();
148
	}
149
150
	$defaults = array(
151
		'status'  => 1,
152
		'post_id' => $post_id,
153
		'order'   => 'ASC',
154
	);
155
	$r = wp_parse_args( $args, $defaults );
156
157
	$query = new WP_Comment_Query;
158
	return $query->query( $r );
159
}
160
161
/**
162
 * Retrieves comment data given a comment ID or comment object.
163
 *
164
 * If an object is passed then the comment data will be cached and then returned
165
 * after being passed through a filter. If the comment is empty, then the global
166
 * comment variable will be used, if it is set.
167
 *
168
 * @since 2.0.0
169
 *
170
 * @global WP_Comment $comment
171
 *
172
 * @param WP_Comment|string|int $comment Comment to retrieve.
173
 * @param string                $output  Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
174
 *                                       a WP_Comment object, an associative array, or a numeric array, respectively. Default OBJECT.
175
 * @return WP_Comment|array|null Depends on $output value.
176
 */
177
function get_comment( &$comment = null, $output = OBJECT ) {
178
	if ( empty( $comment ) && isset( $GLOBALS['comment'] ) ) {
179
		$comment = $GLOBALS['comment'];
180
	}
181
182
	if ( $comment instanceof WP_Comment ) {
183
		$_comment = $comment;
184
	} elseif ( is_object( $comment ) ) {
185
		$_comment = new WP_Comment( $comment );
186
	} else {
187
		$_comment = WP_Comment::get_instance( $comment );
188
	}
189
190
	if ( ! $_comment ) {
191
		return null;
192
	}
193
194
	/**
195
	 * Fires after a comment is retrieved.
196
	 *
197
	 * @since 2.3.0
198
	 *
199
	 * @param mixed $_comment Comment data.
200
	 */
201
	$_comment = apply_filters( 'get_comment', $_comment );
202
203 View Code Duplication
	if ( $output == OBJECT ) {
204
		return $_comment;
205
	} elseif ( $output == ARRAY_A ) {
206
		return $_comment->to_array();
207
	} elseif ( $output == ARRAY_N ) {
208
		return array_values( $_comment->to_array() );
209
	}
210
	return $_comment;
211
}
212
213
/**
214
 * Retrieve a list of comments.
215
 *
216
 * The comment list can be for the blog as a whole or for an individual post.
217
 *
218
 * @since 2.7.0
219
 *
220
 * @param string|array $args Optional. Array or string of arguments. See WP_Comment_Query::parse_query()
221
 *                           for information on accepted arguments. Default empty.
222
 * @return int|array List of comments or number of found comments if `$count` argument is true.
223
 */
224
function get_comments( $args = '' ) {
225
	$query = new WP_Comment_Query;
226
	return $query->query( $args );
227
}
228
229
/**
230
 * Retrieve all of the WordPress supported comment statuses.
231
 *
232
 * Comments have a limited set of valid status values, this provides the comment
233
 * status values and descriptions.
234
 *
235
 * @since 2.7.0
236
 *
237
 * @return array List of comment statuses.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
238
 */
239
function get_comment_statuses() {
240
	$status = array(
241
		'hold'		=> __( 'Unapproved' ),
242
		'approve'	=> _x( 'Approved', 'comment status' ),
243
		'spam'		=> _x( 'Spam', 'comment status' ),
244
		'trash'		=> _x( 'Trash', 'comment status' ),
245
	);
246
247
	return $status;
248
}
249
250
/**
251
 * Gets the default comment status for a post type.
252
 *
253
 * @since 4.3.0
254
 *
255
 * @param string $post_type    Optional. Post type. Default 'post'.
256
 * @param string $comment_type Optional. Comment type. Default 'comment'.
257
 * @return string Expected return value is 'open' or 'closed'.
258
 */
259
function get_default_comment_status( $post_type = 'post', $comment_type = 'comment' ) {
260
	switch ( $comment_type ) {
261
		case 'pingback' :
262
		case 'trackback' :
263
			$supports = 'trackbacks';
264
			$option = 'ping';
265
			break;
266
		default :
267
			$supports = 'comments';
268
			$option = 'comment';
269
	}
270
271
	// Set the status.
272
	if ( 'page' === $post_type ) {
273
		$status = 'closed';
274
	} elseif ( post_type_supports( $post_type, $supports ) ) {
275
		$status = get_option( "default_{$option}_status" );
276
	} else {
277
		$status = 'closed';
278
	}
279
280
	/**
281
	 * Filters the default comment status for the given post type.
282
	 *
283
	 * @since 4.3.0
284
	 *
285
	 * @param string $status       Default status for the given post type,
286
	 *                             either 'open' or 'closed'.
287
	 * @param string $post_type    Post type. Default is `post`.
288
	 * @param string $comment_type Type of comment. Default is `comment`.
289
	 */
290
	return apply_filters( 'get_default_comment_status' , $status, $post_type, $comment_type );
291
}
292
293
/**
294
 * The date the last comment was modified.
295
 *
296
 * @since 1.5.0
297
 * @since 4.7.0 Replaced caching the modified date in a local static variable
298
 *              with the Object Cache API.
299
 *
300
 * @global wpdb $wpdb WordPress database abstraction object.
301
 *
302
 * @param string $timezone Which timezone to use in reference to 'gmt', 'blog', or 'server' locations.
303
 * @return string|false Last comment modified date on success, false on failure.
304
 */
305
function get_lastcommentmodified( $timezone = 'server' ) {
306
	global $wpdb;
307
308
	$timezone = strtolower( $timezone );
309
	$key = "lastcommentmodified:$timezone";
310
311
	$comment_modified_date = wp_cache_get( $key, 'timeinfo' );
312
	if ( false !== $comment_modified_date ) {
313
		return $comment_modified_date;
314
	}
315
316
	switch ( $timezone ) {
317
		case 'gmt':
318
			$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" );
319
			break;
320
		case 'blog':
321
			$comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
322
			break;
323
		case 'server':
324
			$add_seconds_server = date( 'Z' );
325
326
			$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 ) );
327
			break;
328
	}
329
330
	if ( $comment_modified_date ) {
331
		wp_cache_set( $key, $comment_modified_date, 'timeinfo' );
332
333
		return $comment_modified_date;
334
	}
335
336
	return false;
337
}
338
339
/**
340
 * The amount of comments in a post or total comments.
341
 *
342
 * A lot like wp_count_comments(), in that they both return comment stats (albeit with different types).
343
 * The wp_count_comments() actually caches, but this function does not.
344
 *
345
 * @since 2.0.0
346
 *
347
 * @global wpdb $wpdb WordPress database abstraction object.
348
 *
349
 * @param int $post_id Optional. Comment amount in post if > 0, else total comments blog wide.
350
 * @return array The amount of spam, approved, awaiting moderation, and total comments.
351
 */
352
function get_comment_count( $post_id = 0 ) {
353
	global $wpdb;
354
355
	$post_id = (int) $post_id;
356
357
	$where = '';
358
	if ( $post_id > 0 ) {
359
		$where = $wpdb->prepare("WHERE comment_post_ID = %d", $post_id);
360
	}
361
362
	$totals = (array) $wpdb->get_results("
363
		SELECT comment_approved, COUNT( * ) AS total
364
		FROM {$wpdb->comments}
365
		{$where}
366
		GROUP BY comment_approved
367
	", ARRAY_A);
368
369
	$comment_count = array(
370
		'approved'            => 0,
371
		'awaiting_moderation' => 0,
372
		'spam'                => 0,
373
		'trash'               => 0,
374
		'post-trashed'        => 0,
375
		'total_comments'      => 0,
376
		'all'                 => 0,
377
	);
378
379
	foreach ( $totals as $row ) {
380
		switch ( $row['comment_approved'] ) {
381
			case 'trash':
382
				$comment_count['trash'] = $row['total'];
383
				break;
384
			case 'post-trashed':
385
				$comment_count['post-trashed'] = $row['total'];
386
				break;
387
			case 'spam':
388
				$comment_count['spam'] = $row['total'];
389
				$comment_count['total_comments'] += $row['total'];
390
				break;
391 View Code Duplication
			case '1':
392
				$comment_count['approved'] = $row['total'];
393
				$comment_count['total_comments'] += $row['total'];
394
				$comment_count['all'] += $row['total'];
395
				break;
396 View Code Duplication
			case '0':
397
				$comment_count['awaiting_moderation'] = $row['total'];
398
				$comment_count['total_comments'] += $row['total'];
399
				$comment_count['all'] += $row['total'];
400
				break;
401
			default:
402
				break;
403
		}
404
	}
405
406
	return $comment_count;
407
}
408
409
//
410
// Comment meta functions
411
//
412
413
/**
414
 * Add meta data field to a comment.
415
 *
416
 * @since 2.9.0
417
 * @link https://codex.wordpress.org/Function_Reference/add_comment_meta
418
 *
419
 * @param int $comment_id Comment ID.
420
 * @param string $meta_key Metadata name.
421
 * @param mixed $meta_value Metadata value.
422
 * @param bool $unique Optional, default is false. Whether the same key should not be added.
423
 * @return int|bool Meta ID on success, false on failure.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use integer|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
424
 */
425
function add_comment_meta($comment_id, $meta_key, $meta_value, $unique = false) {
426
	return add_metadata('comment', $comment_id, $meta_key, $meta_value, $unique);
427
}
428
429
/**
430
 * Remove metadata matching criteria from a comment.
431
 *
432
 * You can match based on the key, or key and value. Removing based on key and
433
 * value, will keep from removing duplicate metadata with the same key. It also
434
 * allows removing all metadata matching key, if needed.
435
 *
436
 * @since 2.9.0
437
 * @link https://codex.wordpress.org/Function_Reference/delete_comment_meta
438
 *
439
 * @param int $comment_id comment ID
440
 * @param string $meta_key Metadata name.
441
 * @param mixed $meta_value Optional. Metadata value.
442
 * @return bool True on success, false on failure.
443
 */
444
function delete_comment_meta($comment_id, $meta_key, $meta_value = '') {
445
	return delete_metadata('comment', $comment_id, $meta_key, $meta_value);
446
}
447
448
/**
449
 * Retrieve comment meta field for a comment.
450
 *
451
 * @since 2.9.0
452
 * @link https://codex.wordpress.org/Function_Reference/get_comment_meta
453
 *
454
 * @param int $comment_id Comment ID.
455
 * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys.
456
 * @param bool $single Whether to return a single value.
457
 * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
458
 *  is true.
459
 */
460
function get_comment_meta($comment_id, $key = '', $single = false) {
461
	return get_metadata('comment', $comment_id, $key, $single);
462
}
463
464
/**
465
 * Update comment meta field based on comment ID.
466
 *
467
 * Use the $prev_value parameter to differentiate between meta fields with the
468
 * same key and comment ID.
469
 *
470
 * If the meta field for the comment does not exist, it will be added.
471
 *
472
 * @since 2.9.0
473
 * @link https://codex.wordpress.org/Function_Reference/update_comment_meta
474
 *
475
 * @param int $comment_id Comment ID.
476
 * @param string $meta_key Metadata key.
477
 * @param mixed $meta_value Metadata value.
478
 * @param mixed $prev_value Optional. Previous value to check before removing.
479
 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
480
 */
481
function update_comment_meta($comment_id, $meta_key, $meta_value, $prev_value = '') {
482
	return update_metadata('comment', $comment_id, $meta_key, $meta_value, $prev_value);
483
}
484
485
/**
486
 * Queues comments for metadata lazy-loading.
487
 *
488
 * @since 4.5.0
489
 *
490
 * @param array $comments Array of comment objects.
491
 */
492
function wp_queue_comments_for_comment_meta_lazyload( $comments ) {
493
	// Don't use `wp_list_pluck()` to avoid by-reference manipulation.
494
	$comment_ids = array();
495
	if ( is_array( $comments ) ) {
496
		foreach ( $comments as $comment ) {
497
			if ( $comment instanceof WP_Comment ) {
498
				$comment_ids[] = $comment->comment_ID;
499
			}
500
		}
501
	}
502
503
	if ( $comment_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $comment_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
504
		$lazyloader = wp_metadata_lazyloader();
505
		$lazyloader->queue_objects( 'comment', $comment_ids );
506
	}
507
}
508
509
/**
510
 * Sets the cookies used to store an unauthenticated commentator's identity. Typically used
511
 * to recall previous comments by this commentator that are still held in moderation.
512
 *
513
 * @param WP_Comment $comment Comment object.
514
 * @param object     $user    Comment author's object.
515
 *
516
 * @since 3.4.0
517
 */
518
function wp_set_comment_cookies($comment, $user) {
519
	if ( $user->exists() )
520
		return;
521
522
	/**
523
	 * Filters the lifetime of the comment cookie in seconds.
524
	 *
525
	 * @since 2.8.0
526
	 *
527
	 * @param int $seconds Comment cookie lifetime. Default 30000000.
528
	 */
529
	$comment_cookie_lifetime = apply_filters( 'comment_cookie_lifetime', 30000000 );
530
	$secure = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
531
	setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
532
	setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
533
	setcookie( 'comment_author_url_' . COOKIEHASH, esc_url($comment->comment_author_url), time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
534
}
535
536
/**
537
 * Sanitizes the cookies sent to the user already.
538
 *
539
 * Will only do anything if the cookies have already been created for the user.
540
 * Mostly used after cookies had been sent to use elsewhere.
541
 *
542
 * @since 2.0.4
543
 */
544
function sanitize_comment_cookies() {
545 View Code Duplication
	if ( isset( $_COOKIE['comment_author_' . COOKIEHASH] ) ) {
546
		/**
547
		 * Filters the comment author's name cookie before it is set.
548
		 *
549
		 * When this filter hook is evaluated in wp_filter_comment(),
550
		 * the comment author's name string is passed.
551
		 *
552
		 * @since 1.5.0
553
		 *
554
		 * @param string $author_cookie The comment author name cookie.
555
		 */
556
		$comment_author = apply_filters( 'pre_comment_author_name', $_COOKIE['comment_author_' . COOKIEHASH] );
557
		$comment_author = wp_unslash($comment_author);
558
		$comment_author = esc_attr($comment_author);
0 ignored issues
show
It seems like $comment_author can also be of type array; however, esc_attr() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
559
		$_COOKIE['comment_author_' . COOKIEHASH] = $comment_author;
560
	}
561
562 View Code Duplication
	if ( isset( $_COOKIE['comment_author_email_' . COOKIEHASH] ) ) {
563
		/**
564
		 * Filters the comment author's email cookie before it is set.
565
		 *
566
		 * When this filter hook is evaluated in wp_filter_comment(),
567
		 * the comment author's email string is passed.
568
		 *
569
		 * @since 1.5.0
570
		 *
571
		 * @param string $author_email_cookie The comment author email cookie.
572
		 */
573
		$comment_author_email = apply_filters( 'pre_comment_author_email', $_COOKIE['comment_author_email_' . COOKIEHASH] );
574
		$comment_author_email = wp_unslash($comment_author_email);
575
		$comment_author_email = esc_attr($comment_author_email);
0 ignored issues
show
It seems like $comment_author_email can also be of type array; however, esc_attr() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
576
		$_COOKIE['comment_author_email_'.COOKIEHASH] = $comment_author_email;
577
	}
578
579 View Code Duplication
	if ( isset( $_COOKIE['comment_author_url_' . COOKIEHASH] ) ) {
580
		/**
581
		 * Filters the comment author's URL cookie before it is set.
582
		 *
583
		 * When this filter hook is evaluated in wp_filter_comment(),
584
		 * the comment author's URL string is passed.
585
		 *
586
		 * @since 1.5.0
587
		 *
588
		 * @param string $author_url_cookie The comment author URL cookie.
589
		 */
590
		$comment_author_url = apply_filters( 'pre_comment_author_url', $_COOKIE['comment_author_url_' . COOKIEHASH] );
591
		$comment_author_url = wp_unslash($comment_author_url);
592
		$_COOKIE['comment_author_url_'.COOKIEHASH] = $comment_author_url;
593
	}
594
}
595
596
/**
597
 * Validates whether this comment is allowed to be made.
598
 *
599
 * @since 2.0.0
600
 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function to
601
 *              return a WP_Error object instead of dying.
602
 *
603
 * @global wpdb $wpdb WordPress database abstraction object.
604
 *
605
 * @param array $commentdata Contains information on the comment.
606
 * @param bool  $avoid_die   When true, a disallowed comment will result in the function
607
 *                           returning a WP_Error object, rather than executing wp_die().
608
 *                           Default false.
609
 * @return int|string|WP_Error Allowed comments return the approval status (0|1|'spam').
610
 *                             If `$avoid_die` is true, disallowed comments return a WP_Error.
611
 */
612
function wp_allow_comment( $commentdata, $avoid_die = false ) {
613
	global $wpdb;
614
615
	// Simple duplicate check
616
	// expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
617
	$dupe = $wpdb->prepare(
618
		"SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = %s AND comment_approved != 'trash' AND ( comment_author = %s ",
619
		wp_unslash( $commentdata['comment_post_ID'] ),
620
		wp_unslash( $commentdata['comment_parent'] ),
621
		wp_unslash( $commentdata['comment_author'] )
622
	);
623
	if ( $commentdata['comment_author_email'] ) {
624
		$dupe .= $wpdb->prepare(
625
			"AND comment_author_email = %s ",
626
			wp_unslash( $commentdata['comment_author_email'] )
627
		);
628
	}
629
	$dupe .= $wpdb->prepare(
630
		") AND comment_content = %s LIMIT 1",
631
		wp_unslash( $commentdata['comment_content'] )
632
	);
633
634
	$dupe_id = $wpdb->get_var( $dupe );
635
636
	/**
637
	 * Filters the ID, if any, of the duplicate comment found when creating a new comment.
638
	 *
639
	 * Return an empty value from this filter to allow what WP considers a duplicate comment.
640
	 *
641
	 * @since 4.4.0
642
	 *
643
	 * @param int   $dupe_id     ID of the comment identified as a duplicate.
644
	 * @param array $commentdata Data for the comment being created.
645
	 */
646
	$dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata );
647
648
	if ( $dupe_id ) {
649
		/**
650
		 * Fires immediately after a duplicate comment is detected.
651
		 *
652
		 * @since 3.0.0
653
		 *
654
		 * @param array $commentdata Comment data.
655
		 */
656
		do_action( 'comment_duplicate_trigger', $commentdata );
657
		if ( true === $avoid_die ) {
658
			return new WP_Error( 'comment_duplicate', __( 'Duplicate comment detected; it looks as though you&#8217;ve already said that!' ), 409 );
659
		} else {
660
			if ( wp_doing_ajax() ) {
661
				die( __('Duplicate comment detected; it looks as though you&#8217;ve already said that!') );
0 ignored issues
show
Coding Style Compatibility introduced by
The function wp_allow_comment() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
662
			}
663
664
			wp_die( __( 'Duplicate comment detected; it looks as though you&#8217;ve already said that!' ), 409 );
665
		}
666
	}
667
668
	/**
669
	 * Fires immediately before a comment is marked approved.
670
	 *
671
	 * Allows checking for comment flooding.
672
	 *
673
	 * @since 2.3.0
674
	 * @since 4.7.0 The `$avoid_die` parameter was added.
675
	 *
676
	 * @param string $comment_author_IP    Comment author's IP address.
677
	 * @param string $comment_author_email Comment author's email.
678
	 * @param string $comment_date_gmt     GMT date the comment was posted.
679
	 * @param bool   $avoid_die            Whether to prevent executing wp_die()
680
	 *                                     or die() if a comment flood is occurring.
681
	 */
682
	do_action(
683
		'check_comment_flood',
684
		$commentdata['comment_author_IP'],
685
		$commentdata['comment_author_email'],
686
		$commentdata['comment_date_gmt'],
687
		$avoid_die
688
	);
689
690
	/**
691
	 * Filters whether a comment is part of a comment flood.
692
	 *
693
	 * The default check is wp_check_comment_flood(). See check_comment_flood_db().
694
	 *
695
	 * @since 4.7.0
696
	 *
697
	 * @param bool   $is_flood             Is a comment flooding occurring? Default false.
698
	 * @param string $comment_author_IP    Comment author's IP address.
699
	 * @param string $comment_author_email Comment author's email.
700
	 * @param string $comment_date_gmt     GMT date the comment was posted.
701
	 * @param bool   $avoid_die            Whether to prevent executing wp_die()
702
	 *                                     or die() if a comment flood is occurring.
703
	 */
704
	$is_flood = apply_filters(
705
		'wp_is_comment_flood',
706
		false,
707
		$commentdata['comment_author_IP'],
708
		$commentdata['comment_author_email'],
709
		$commentdata['comment_date_gmt'],
710
		$avoid_die
711
	);
712
713
	if ( $is_flood ) {
714
		return new WP_Error( 'comment_flood', __( 'You are posting comments too quickly. Slow down.' ), 429 );
715
	}
716
717
	if ( ! empty( $commentdata['user_id'] ) ) {
718
		$user = get_userdata( $commentdata['user_id'] );
719
		$post_author = $wpdb->get_var( $wpdb->prepare(
720
			"SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
721
			$commentdata['comment_post_ID']
722
		) );
723
	}
724
725
	if ( isset( $user ) && ( $commentdata['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
0 ignored issues
show
The variable $post_author does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
726
		// The author and the admins get respect.
727
		$approved = 1;
728
	} else {
729
		// Everyone else's comments will be checked.
730
		if ( check_comment(
731
			$commentdata['comment_author'],
732
			$commentdata['comment_author_email'],
733
			$commentdata['comment_author_url'],
734
			$commentdata['comment_content'],
735
			$commentdata['comment_author_IP'],
736
			$commentdata['comment_agent'],
737
			$commentdata['comment_type']
738
		) ) {
739
			$approved = 1;
740
		} else {
741
			$approved = 0;
742
		}
743
744
		if ( wp_blacklist_check(
745
			$commentdata['comment_author'],
746
			$commentdata['comment_author_email'],
747
			$commentdata['comment_author_url'],
748
			$commentdata['comment_content'],
749
			$commentdata['comment_author_IP'],
750
			$commentdata['comment_agent']
751
		) ) {
752
			$approved = EMPTY_TRASH_DAYS ? 'trash' : 'spam';
753
		}
754
	}
755
756
	/**
757
	 * Filters a comment's approval status before it is set.
758
	 *
759
	 * @since 2.1.0
760
	 *
761
	 * @param bool|string $approved    The approval status. Accepts 1, 0, or 'spam'.
762
	 * @param array       $commentdata Comment data.
763
	 */
764
	$approved = apply_filters( 'pre_comment_approved', $approved, $commentdata );
765
	return $approved;
766
}
767
768
/**
769
 * Hooks WP's native database-based comment-flood check.
770
 *
771
 * This wrapper maintains backward compatibility with plugins that expect to
772
 * be able to unhook the legacy check_comment_flood_db() function from
773
 * 'check_comment_flood' using remove_action().
774
 *
775
 * @since 2.3.0
776
 * @since 4.7.0 Converted to be an add_filter() wrapper.
777
 */
778
function check_comment_flood_db() {
779
	add_filter( 'wp_is_comment_flood', 'wp_check_comment_flood', 10, 5 );
780
}
781
782
/**
783
 * Checks whether comment flooding is occurring.
784
 *
785
 * Won't run, if current user can manage options, so to not block
786
 * administrators.
787
 *
788
 * @since 4.7.0
789
 *
790
 * @global wpdb $wpdb WordPress database abstraction object.
791
 *
792
 * @param bool   $is_flood  Is a comment flooding occurring?
793
 * @param string $ip        Comment IP.
794
 * @param string $email     Comment author email address.
795
 * @param string $date      MySQL time string.
796
 * @param bool   $avoid_die When true, a disallowed comment will result in the function
797
 *                          returning a WP_Error object, rather than executing wp_die().
798
 *                          Default false.
799
 * @return bool Whether comment flooding is occurring.
0 ignored issues
show
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
800
 */
801
function wp_check_comment_flood( $is_flood, $ip, $email, $date, $avoid_die = false ) {
802
803
	global $wpdb;
804
805
	// Another callback has declared a flood. Trust it.
806
	if ( true === $is_flood ) {
807
		return $is_flood;
808
	}
809
810
	// don't throttle admins or moderators
811
	if ( current_user_can( 'manage_options' ) || current_user_can( 'moderate_comments' ) ) {
812
		return false;
813
	}
814
	$hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
815
816
	if ( is_user_logged_in() ) {
817
		$user = get_current_user_id();
818
		$check_column = '`user_id`';
819
	} else {
820
		$user = $ip;
821
		$check_column = '`comment_author_IP`';
822
	}
823
824
	$sql = $wpdb->prepare(
825
		"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",
826
		$hour_ago,
827
		$user,
828
		$email
829
	);
830
	$lasttime = $wpdb->get_var( $sql );
831
	if ( $lasttime ) {
832
		$time_lastcomment = mysql2date('U', $lasttime, false);
833
		$time_newcomment  = mysql2date('U', $date, false);
834
		/**
835
		 * Filters the comment flood status.
836
		 *
837
		 * @since 2.1.0
838
		 *
839
		 * @param bool $bool             Whether a comment flood is occurring. Default false.
840
		 * @param int  $time_lastcomment Timestamp of when the last comment was posted.
841
		 * @param int  $time_newcomment  Timestamp of when the new comment was posted.
842
		 */
843
		$flood_die = apply_filters( 'comment_flood_filter', false, $time_lastcomment, $time_newcomment );
844
		if ( $flood_die ) {
845
			/**
846
			 * Fires before the comment flood message is triggered.
847
			 *
848
			 * @since 1.5.0
849
			 *
850
			 * @param int $time_lastcomment Timestamp of when the last comment was posted.
851
			 * @param int $time_newcomment  Timestamp of when the new comment was posted.
852
			 */
853
			do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
854
			if ( true === $avoid_die ) {
855
				return true;
856
			} else {
857
				if ( wp_doing_ajax() ) {
858
					die( __('You are posting comments too quickly. Slow down.') );
0 ignored issues
show
Coding Style Compatibility introduced by
The function wp_check_comment_flood() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
859
				}
860
861
				wp_die( __( 'You are posting comments too quickly. Slow down.' ), 429 );
862
			}
863
		}
864
	}
865
866
	return false;
867
}
868
869
/**
870
 * Separates an array of comments into an array keyed by comment_type.
871
 *
872
 * @since 2.7.0
873
 *
874
 * @param array $comments Array of comments
875
 * @return array Array of comments keyed by comment_type.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
876
 */
877
function separate_comments(&$comments) {
878
	$comments_by_type = array('comment' => array(), 'trackback' => array(), 'pingback' => array(), 'pings' => array());
879
	$count = count($comments);
880
	for ( $i = 0; $i < $count; $i++ ) {
881
		$type = $comments[$i]->comment_type;
882
		if ( empty($type) )
883
			$type = 'comment';
884
		$comments_by_type[$type][] = &$comments[$i];
885
		if ( 'trackback' == $type || 'pingback' == $type )
886
			$comments_by_type['pings'][] = &$comments[$i];
887
	}
888
889
	return $comments_by_type;
890
}
891
892
/**
893
 * Calculate the total number of comment pages.
894
 *
895
 * @since 2.7.0
896
 *
897
 * @uses Walker_Comment
898
 *
899
 * @global WP_Query $wp_query
900
 *
901
 * @param array $comments Optional array of WP_Comment objects. Defaults to $wp_query->comments
0 ignored issues
show
Should the type for parameter $comments not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
902
 * @param int   $per_page Optional comments per page.
903
 * @param bool  $threaded Optional control over flat or threaded comments.
904
 * @return int Number of comment pages.
905
 */
906
function get_comment_pages_count( $comments = null, $per_page = null, $threaded = null ) {
907
	global $wp_query;
908
909
	if ( null === $comments && null === $per_page && null === $threaded && !empty($wp_query->max_num_comment_pages) )
910
		return $wp_query->max_num_comment_pages;
911
912
	if ( ( ! $comments || ! is_array( $comments ) ) && ! empty( $wp_query->comments )  )
913
		$comments = $wp_query->comments;
914
915
	if ( empty($comments) )
916
		return 0;
917
918
	if ( ! get_option( 'page_comments' ) ) {
919
		return 1;
920
	}
921
922
	if ( !isset($per_page) )
923
		$per_page = (int) get_query_var('comments_per_page');
924
	if ( 0 === $per_page )
925
		$per_page = (int) get_option('comments_per_page');
926
	if ( 0 === $per_page )
927
		return 1;
928
929
	if ( !isset($threaded) )
930
		$threaded = get_option('thread_comments');
931
932
	if ( $threaded ) {
933
		$walker = new Walker_Comment;
934
		$count = ceil( $walker->get_number_of_root_elements( $comments ) / $per_page );
935
	} else {
936
		$count = ceil( count( $comments ) / $per_page );
937
	}
938
939
	return $count;
940
}
941
942
/**
943
 * Calculate what page number a comment will appear on for comment paging.
944
 *
945
 * @since 2.7.0
946
 *
947
 * @global wpdb $wpdb WordPress database abstraction object.
948
 *
949
 * @param int   $comment_ID Comment ID.
950
 * @param array $args {
951
 *      Array of optional arguments.
952
 *      @type string     $type      Limit paginated comments to those matching a given type. Accepts 'comment',
953
 *                                  'trackback', 'pingback', 'pings' (trackbacks and pingbacks), or 'all'.
954
 *                                  Default is 'all'.
955
 *      @type int        $per_page  Per-page count to use when calculating pagination. Defaults to the value of the
956
 *                                  'comments_per_page' option.
957
 *      @type int|string $max_depth If greater than 1, comment page will be determined for the top-level parent of
958
 *                                  `$comment_ID`. Defaults to the value of the 'thread_comments_depth' option.
959
 * } *
960
 * @return int|null Comment page number or null on error.
961
 */
962
function get_page_of_comment( $comment_ID, $args = array() ) {
963
	global $wpdb;
964
965
	$page = null;
966
967
	if ( !$comment = get_comment( $comment_ID ) )
968
		return;
969
970
	$defaults = array( 'type' => 'all', 'page' => '', 'per_page' => '', 'max_depth' => '' );
971
	$args = wp_parse_args( $args, $defaults );
972
	$original_args = $args;
973
974
	// Order of precedence: 1. `$args['per_page']`, 2. 'comments_per_page' query_var, 3. 'comments_per_page' option.
975
	if ( get_option( 'page_comments' ) ) {
976
		if ( '' === $args['per_page'] ) {
977
			$args['per_page'] = get_query_var( 'comments_per_page' );
978
		}
979
980
		if ( '' === $args['per_page'] ) {
981
			$args['per_page'] = get_option( 'comments_per_page' );
982
		}
983
	}
984
985
	if ( empty($args['per_page']) ) {
986
		$args['per_page'] = 0;
987
		$args['page'] = 0;
988
	}
989
990
	if ( $args['per_page'] < 1 ) {
991
		$page = 1;
992
	}
993
994
	if ( null === $page ) {
995 View Code Duplication
		if ( '' === $args['max_depth'] ) {
996
			if ( get_option('thread_comments') )
997
				$args['max_depth'] = get_option('thread_comments_depth');
998
			else
999
				$args['max_depth'] = -1;
1000
		}
1001
1002
		// Find this comment's top level parent if threading is enabled
1003
		if ( $args['max_depth'] > 1 && 0 != $comment->comment_parent )
1004
			return get_page_of_comment( $comment->comment_parent, $args );
1005
1006
		$comment_args = array(
1007
			'type'       => $args['type'],
1008
			'post_id'    => $comment->comment_post_ID,
1009
			'fields'     => 'ids',
1010
			'count'      => true,
1011
			'status'     => 'approve',
1012
			'parent'     => 0,
1013
			'date_query' => array(
1014
				array(
1015
					'column' => "$wpdb->comments.comment_date_gmt",
1016
					'before' => $comment->comment_date_gmt,
1017
				)
1018
			),
1019
		);
1020
1021
		$comment_query = new WP_Comment_Query();
1022
		$older_comment_count = $comment_query->query( $comment_args );
1023
1024
		// No older comments? Then it's page #1.
1025
		if ( 0 == $older_comment_count ) {
1026
			$page = 1;
1027
1028
		// Divide comments older than this one by comments per page to get this comment's page number
1029
		} else {
1030
			$page = ceil( ( $older_comment_count + 1 ) / $args['per_page'] );
1031
		}
1032
	}
1033
1034
	/**
1035
	 * Filters the calculated page on which a comment appears.
1036
	 *
1037
	 * @since 4.4.0
1038
	 * @since 4.7.0 Introduced the `$comment_ID` parameter.
1039
	 *
1040
	 * @param int   $page          Comment page.
1041
	 * @param array $args {
1042
	 *     Arguments used to calculate pagination. These include arguments auto-detected by the function,
1043
	 *     based on query vars, system settings, etc. For pristine arguments passed to the function,
1044
	 *     see `$original_args`.
1045
	 *
1046
	 *     @type string $type      Type of comments to count.
1047
	 *     @type int    $page      Calculated current page.
1048
	 *     @type int    $per_page  Calculated number of comments per page.
1049
	 *     @type int    $max_depth Maximum comment threading depth allowed.
1050
	 * }
1051
	 * @param array $original_args {
1052
	 *     Array of arguments passed to the function. Some or all of these may not be set.
1053
	 *
1054
	 *     @type string $type      Type of comments to count.
1055
	 *     @type int    $page      Current comment page.
1056
	 *     @type int    $per_page  Number of comments per page.
1057
	 *     @type int    $max_depth Maximum comment threading depth allowed.
1058
	 * }
1059
	 * @param int $comment_ID ID of the comment.
1060
	 */
1061
	return apply_filters( 'get_page_of_comment', (int) $page, $args, $original_args, $comment_ID );
1062
}
1063
1064
/**
1065
 * Retrieves the maximum character lengths for the comment form fields.
1066
 *
1067
 * @since 4.5.0
1068
 *
1069
 * @global wpdb $wpdb WordPress database abstraction object.
1070
 *
1071
 * @return array Maximum character length for the comment form fields.
1072
 */
1073
function wp_get_comment_fields_max_lengths() {
1074
	global $wpdb;
1075
1076
	$lengths = array(
1077
		'comment_author'       => 245,
1078
		'comment_author_email' => 100,
1079
		'comment_author_url'   => 200,
1080
		'comment_content'      => 65525,
1081
	);
1082
1083
	if ( $wpdb->is_mysql ) {
1084
		foreach ( $lengths as $column => $length ) {
1085
			$col_length = $wpdb->get_col_length( $wpdb->comments, $column );
1086
			$max_length = 0;
1087
1088
			// No point if we can't get the DB column lengths
1089
			if ( is_wp_error( $col_length ) ) {
1090
				break;
1091
			}
1092
1093
			if ( ! is_array( $col_length ) && (int) $col_length > 0 ) {
1094
				$max_length = (int) $col_length;
1095
			} elseif ( is_array( $col_length ) && isset( $col_length['length'] ) && intval( $col_length['length'] ) > 0 ) {
1096
				$max_length = (int) $col_length['length'];
1097
1098
				if ( ! empty( $col_length['type'] ) && 'byte' === $col_length['type'] ) {
1099
					$max_length = $max_length - 10;
1100
				}
1101
			}
1102
1103
			if ( $max_length > 0 ) {
1104
				$lengths[ $column ] = $max_length;
1105
			}
1106
		}
1107
	}
1108
1109
	/**
1110
	 * Filters the lengths for the comment form fields.
1111
	 *
1112
	 * @since 4.5.0
1113
	 *
1114
	 * @param array $lengths Associative array `'field_name' => 'maximum length'`.
1115
	 */
1116
	return apply_filters( 'wp_get_comment_fields_max_lengths', $lengths );
1117
}
1118
1119
/**
1120
 * Compares the lengths of comment data against the maximum character limits.
1121
 *
1122
 * @since 4.7.0
1123
 *
1124
 * @param array $comment_data Array of arguments for inserting a comment.
1125
 * @return WP_Error|true WP_Error when a comment field exceeds the limit,
0 ignored issues
show
Should the return type not be WP_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1126
 *                       otherwise true.
1127
 */
1128
function wp_check_comment_data_max_lengths( $comment_data ) {
1129
	$max_lengths = wp_get_comment_fields_max_lengths();
1130
1131 View Code Duplication
	if ( isset( $comment_data['comment_author'] ) && mb_strlen( $comment_data['comment_author'], '8bit' ) > $max_lengths['comment_author'] ) {
1132
		return new WP_Error( 'comment_author_column_length', __( '<strong>ERROR</strong>: your name is too long.' ), 200 );
1133
	}
1134
1135 View Code Duplication
	if ( isset( $comment_data['comment_author_email'] ) && strlen( $comment_data['comment_author_email'] ) > $max_lengths['comment_author_email'] ) {
1136
		return new WP_Error( 'comment_author_email_column_length', __( '<strong>ERROR</strong>: your email address is too long.' ), 200 );
1137
	}
1138
1139 View Code Duplication
	if ( isset( $comment_data['comment_author_url'] ) && strlen( $comment_data['comment_author_url'] ) > $max_lengths['comment_author_url'] ) {
1140
		return new WP_Error( 'comment_author_url_column_length', __( '<strong>ERROR</strong>: your url is too long.' ), 200 );
1141
	}
1142
1143 View Code Duplication
	if ( isset( $comment_data['comment_content'] ) && mb_strlen( $comment_data['comment_content'], '8bit' ) > $max_lengths['comment_content'] ) {
1144
		return new WP_Error( 'comment_content_column_length', __( '<strong>ERROR</strong>: your comment is too long.' ), 200 );
1145
	}
1146
1147
	return true;
1148
}
1149
1150
/**
1151
 * Does comment contain blacklisted characters or words.
1152
 *
1153
 * @since 1.5.0
1154
 *
1155
 * @param string $author The author of the comment
1156
 * @param string $email The email of the comment
1157
 * @param string $url The url used in the comment
1158
 * @param string $comment The comment content
1159
 * @param string $user_ip The comment author IP address
1160
 * @param string $user_agent The author's browser user agent
1161
 * @return bool True if comment contains blacklisted content, false if comment does not
1162
 */
1163
function wp_blacklist_check($author, $email, $url, $comment, $user_ip, $user_agent) {
1164
	/**
1165
	 * Fires before the comment is tested for blacklisted characters or words.
1166
	 *
1167
	 * @since 1.5.0
1168
	 *
1169
	 * @param string $author     Comment author.
1170
	 * @param string $email      Comment author's email.
1171
	 * @param string $url        Comment author's URL.
1172
	 * @param string $comment    Comment content.
1173
	 * @param string $user_ip    Comment author's IP address.
1174
	 * @param string $user_agent Comment author's browser user agent.
1175
	 */
1176
	do_action( 'wp_blacklist_check', $author, $email, $url, $comment, $user_ip, $user_agent );
1177
1178
	$mod_keys = trim( get_option('blacklist_keys') );
1179
	if ( '' == $mod_keys )
1180
		return false; // If moderation keys are empty
1181
1182
	// Ensure HTML tags are not being used to bypass the blacklist.
1183
	$comment_without_html = wp_strip_all_tags( $comment );
1184
1185
	$words = explode("\n", $mod_keys );
1186
1187
	foreach ( (array) $words as $word ) {
1188
		$word = trim($word);
1189
1190
		// Skip empty lines
1191
		if ( empty($word) ) { continue; }
1192
1193
		// Do some escaping magic so that '#' chars in the
1194
		// spam words don't break things:
1195
		$word = preg_quote($word, '#');
1196
1197
		$pattern = "#$word#i";
1198
		if (
1199
			   preg_match($pattern, $author)
1200
			|| preg_match($pattern, $email)
1201
			|| preg_match($pattern, $url)
1202
			|| preg_match($pattern, $comment)
1203
			|| preg_match($pattern, $comment_without_html)
1204
			|| preg_match($pattern, $user_ip)
1205
			|| preg_match($pattern, $user_agent)
1206
		 )
1207
			return true;
1208
	}
1209
	return false;
1210
}
1211
1212
/**
1213
 * Retrieve total comments for blog or single post.
1214
 *
1215
 * The properties of the returned object contain the 'moderated', 'approved',
1216
 * and spam comments for either the entire blog or single post. Those properties
1217
 * contain the amount of comments that match the status. The 'total_comments'
1218
 * property contains the integer of total comments.
1219
 *
1220
 * The comment stats are cached and then retrieved, if they already exist in the
1221
 * cache.
1222
 *
1223
 * @since 2.5.0
1224
 *
1225
 * @param int $post_id Optional. Post ID.
1226
 * @return object|array Comment stats.
1227
 */
1228
function wp_count_comments( $post_id = 0 ) {
1229
	$post_id = (int) $post_id;
1230
1231
	/**
1232
	 * Filters the comments count for a given post.
1233
	 *
1234
	 * @since 2.7.0
1235
	 *
1236
	 * @param array $count   An empty array.
1237
	 * @param int   $post_id The post ID.
1238
	 */
1239
	$filtered = apply_filters( 'wp_count_comments', array(), $post_id );
1240
	if ( ! empty( $filtered ) ) {
1241
		return $filtered;
1242
	}
1243
1244
	$count = wp_cache_get( "comments-{$post_id}", 'counts' );
1245
	if ( false !== $count ) {
1246
		return $count;
1247
	}
1248
1249
	$stats = get_comment_count( $post_id );
1250
	$stats['moderated'] = $stats['awaiting_moderation'];
1251
	unset( $stats['awaiting_moderation'] );
1252
1253
	$stats_object = (object) $stats;
1254
	wp_cache_set( "comments-{$post_id}", $stats_object, 'counts' );
1255
1256
	return $stats_object;
1257
}
1258
1259
/**
1260
 * Trashes or deletes a comment.
1261
 *
1262
 * The comment is moved to trash instead of permanently deleted unless trash is
1263
 * disabled, item is already in the trash, or $force_delete is true.
1264
 *
1265
 * The post comment count will be updated if the comment was approved and has a
1266
 * post ID available.
1267
 *
1268
 * @since 2.0.0
1269
 *
1270
 * @global wpdb $wpdb WordPress database abstraction object.
1271
 *
1272
 * @param int|WP_Comment $comment_id   Comment ID or WP_Comment object.
1273
 * @param bool           $force_delete Whether to bypass trash and force deletion. Default is false.
1274
 * @return bool True on success, false on failure.
1275
 */
1276
function wp_delete_comment($comment_id, $force_delete = false) {
1277
	global $wpdb;
1278
	if (!$comment = get_comment($comment_id))
1279
		return false;
1280
1281
	if ( !$force_delete && EMPTY_TRASH_DAYS && !in_array( wp_get_comment_status( $comment ), array( 'trash', 'spam' ) ) )
1282
		return wp_trash_comment($comment_id);
1283
1284
	/**
1285
	 * Fires immediately before a comment is deleted from the database.
1286
	 *
1287
	 * @since 1.2.0
1288
	 *
1289
	 * @param int $comment_id The comment ID.
1290
	 */
1291
	do_action( 'delete_comment', $comment->comment_ID );
1292
1293
	// Move children up a level.
1294
	$children = $wpdb->get_col( $wpdb->prepare("SELECT comment_ID FROM $wpdb->comments WHERE comment_parent = %d", $comment->comment_ID) );
1295
	if ( !empty($children) ) {
1296
		$wpdb->update($wpdb->comments, array('comment_parent' => $comment->comment_parent), array('comment_parent' => $comment->comment_ID));
1297
		clean_comment_cache($children);
1298
	}
1299
1300
	// Delete metadata
1301
	$meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) );
1302
	foreach ( $meta_ids as $mid )
1303
		delete_metadata_by_mid( 'comment', $mid );
1304
1305
	if ( ! $wpdb->delete( $wpdb->comments, array( 'comment_ID' => $comment->comment_ID ) ) )
1306
		return false;
1307
1308
	/**
1309
	 * Fires immediately after a comment is deleted from the database.
1310
	 *
1311
	 * @since 2.9.0
1312
	 *
1313
	 * @param int $comment_id The comment ID.
1314
	 */
1315
	do_action( 'deleted_comment', $comment->comment_ID );
1316
1317
	$post_id = $comment->comment_post_ID;
1318
	if ( $post_id && $comment->comment_approved == 1 )
1319
		wp_update_comment_count($post_id);
1320
1321
	clean_comment_cache( $comment->comment_ID );
1322
1323
	/** This action is documented in wp-includes/comment.php */
1324
	do_action( 'wp_set_comment_status', $comment->comment_ID, 'delete' );
1325
1326
	wp_transition_comment_status('delete', $comment->comment_approved, $comment);
1327
	return true;
1328
}
1329
1330
/**
1331
 * Moves a comment to the Trash
1332
 *
1333
 * If trash is disabled, comment is permanently deleted.
1334
 *
1335
 * @since 2.9.0
1336
 *
1337
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1338
 * @return bool True on success, false on failure.
1339
 */
1340
function wp_trash_comment($comment_id) {
1341
	if ( !EMPTY_TRASH_DAYS )
1342
		return wp_delete_comment($comment_id, true);
1343
1344
	if ( !$comment = get_comment($comment_id) )
1345
		return false;
1346
1347
	/**
1348
	 * Fires immediately before a comment is sent to the Trash.
1349
	 *
1350
	 * @since 2.9.0
1351
	 *
1352
	 * @param int $comment_id The comment ID.
1353
	 */
1354
	do_action( 'trash_comment', $comment->comment_ID );
1355
1356 View Code Duplication
	if ( wp_set_comment_status( $comment, 'trash' ) ) {
1357
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1358
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1359
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', $comment->comment_approved );
1360
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_time', time() );
1361
1362
		/**
1363
		 * Fires immediately after a comment is sent to Trash.
1364
		 *
1365
		 * @since 2.9.0
1366
		 *
1367
		 * @param int $comment_id The comment ID.
1368
		 */
1369
		do_action( 'trashed_comment', $comment->comment_ID );
1370
		return true;
1371
	}
1372
1373
	return false;
1374
}
1375
1376
/**
1377
 * Removes a comment from the Trash
1378
 *
1379
 * @since 2.9.0
1380
 *
1381
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1382
 * @return bool True on success, false on failure.
1383
 */
1384 View Code Duplication
function wp_untrash_comment($comment_id) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1385
	$comment = get_comment( $comment_id );
1386
	if ( ! $comment ) {
1387
		return false;
1388
	}
1389
1390
	/**
1391
	 * Fires immediately before a comment is restored from the Trash.
1392
	 *
1393
	 * @since 2.9.0
1394
	 *
1395
	 * @param int $comment_id The comment ID.
1396
	 */
1397
	do_action( 'untrash_comment', $comment->comment_ID );
1398
1399
	$status = (string) get_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', true );
1400
	if ( empty($status) )
1401
		$status = '0';
1402
1403
	if ( wp_set_comment_status( $comment, $status ) ) {
1404
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1405
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1406
		/**
1407
		 * Fires immediately after a comment is restored from the Trash.
1408
		 *
1409
		 * @since 2.9.0
1410
		 *
1411
		 * @param int $comment_id The comment ID.
1412
		 */
1413
		do_action( 'untrashed_comment', $comment->comment_ID );
1414
		return true;
1415
	}
1416
1417
	return false;
1418
}
1419
1420
/**
1421
 * Marks a comment as Spam
1422
 *
1423
 * @since 2.9.0
1424
 *
1425
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1426
 * @return bool True on success, false on failure.
1427
 */
1428
function wp_spam_comment( $comment_id ) {
1429
	$comment = get_comment( $comment_id );
1430
	if ( ! $comment ) {
1431
		return false;
1432
	}
1433
1434
	/**
1435
	 * Fires immediately before a comment is marked as Spam.
1436
	 *
1437
	 * @since 2.9.0
1438
	 *
1439
	 * @param int $comment_id The comment ID.
1440
	 */
1441
	do_action( 'spam_comment', $comment->comment_ID );
1442
1443 View Code Duplication
	if ( wp_set_comment_status( $comment, 'spam' ) ) {
1444
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1445
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1446
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', $comment->comment_approved );
1447
		add_comment_meta( $comment->comment_ID, '_wp_trash_meta_time', time() );
1448
		/**
1449
		 * Fires immediately after a comment is marked as Spam.
1450
		 *
1451
		 * @since 2.9.0
1452
		 *
1453
		 * @param int $comment_id The comment ID.
1454
		 */
1455
		do_action( 'spammed_comment', $comment->comment_ID );
1456
		return true;
1457
	}
1458
1459
	return false;
1460
}
1461
1462
/**
1463
 * Removes a comment from the Spam
1464
 *
1465
 * @since 2.9.0
1466
 *
1467
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1468
 * @return bool True on success, false on failure.
1469
 */
1470 View Code Duplication
function wp_unspam_comment( $comment_id ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1471
	$comment = get_comment( $comment_id );
1472
	if ( ! $comment ) {
1473
		return false;
1474
	}
1475
1476
	/**
1477
	 * Fires immediately before a comment is unmarked as Spam.
1478
	 *
1479
	 * @since 2.9.0
1480
	 *
1481
	 * @param int $comment_id The comment ID.
1482
	 */
1483
	do_action( 'unspam_comment', $comment->comment_ID );
1484
1485
	$status = (string) get_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', true );
1486
	if ( empty($status) )
1487
		$status = '0';
1488
1489
	if ( wp_set_comment_status( $comment, $status ) ) {
1490
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1491
		delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1492
		/**
1493
		 * Fires immediately after a comment is unmarked as Spam.
1494
		 *
1495
		 * @since 2.9.0
1496
		 *
1497
		 * @param int $comment_id The comment ID.
1498
		 */
1499
		do_action( 'unspammed_comment', $comment->comment_ID );
1500
		return true;
1501
	}
1502
1503
	return false;
1504
}
1505
1506
/**
1507
 * The status of a comment by ID.
1508
 *
1509
 * @since 1.0.0
1510
 *
1511
 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object
1512
 * @return false|string Status might be 'trash', 'approved', 'unapproved', 'spam'. False on failure.
1513
 */
1514
function wp_get_comment_status($comment_id) {
1515
	$comment = get_comment($comment_id);
1516
	if ( !$comment )
1517
		return false;
1518
1519
	$approved = $comment->comment_approved;
1520
1521
	if ( $approved == null )
1522
		return false;
1523
	elseif ( $approved == '1' )
1524
		return 'approved';
1525
	elseif ( $approved == '0' )
1526
		return 'unapproved';
1527
	elseif ( $approved == 'spam' )
1528
		return 'spam';
1529
	elseif ( $approved == 'trash' )
1530
		return 'trash';
1531
	else
1532
		return false;
1533
}
1534
1535
/**
1536
 * Call hooks for when a comment status transition occurs.
1537
 *
1538
 * Calls hooks for comment status transitions. If the new comment status is not the same
1539
 * as the previous comment status, then two hooks will be ran, the first is
1540
 * {@see 'transition_comment_status'} with new status, old status, and comment data. The
1541
 * next action called is {@see comment_$old_status_to_$new_status'}. It has the
1542
 * comment data.
1543
 *
1544
 * The final action will run whether or not the comment statuses are the same. The
1545
 * action is named {@see 'comment_$new_status_$comment->comment_type'}.
1546
 *
1547
 * @since 2.7.0
1548
 *
1549
 * @param string $new_status New comment status.
1550
 * @param string $old_status Previous comment status.
1551
 * @param object $comment Comment data.
1552
 */
1553
function wp_transition_comment_status($new_status, $old_status, $comment) {
1554
	/*
1555
	 * Translate raw statuses to human readable formats for the hooks.
1556
	 * This is not a complete list of comment status, it's only the ones
1557
	 * that need to be renamed
1558
	 */
1559
	$comment_statuses = array(
1560
		0         => 'unapproved',
1561
		'hold'    => 'unapproved', // wp_set_comment_status() uses "hold"
1562
		1         => 'approved',
1563
		'approve' => 'approved', // wp_set_comment_status() uses "approve"
1564
	);
1565
	if ( isset($comment_statuses[$new_status]) ) $new_status = $comment_statuses[$new_status];
1566
	if ( isset($comment_statuses[$old_status]) ) $old_status = $comment_statuses[$old_status];
1567
1568
	// Call the hooks
1569
	if ( $new_status != $old_status ) {
1570
		/**
1571
		 * Fires when the comment status is in transition.
1572
		 *
1573
		 * @since 2.7.0
1574
		 *
1575
		 * @param int|string $new_status The new comment status.
1576
		 * @param int|string $old_status The old comment status.
1577
		 * @param object     $comment    The comment data.
1578
		 */
1579
		do_action( 'transition_comment_status', $new_status, $old_status, $comment );
1580
		/**
1581
		 * Fires when the comment status is in transition from one specific status to another.
1582
		 *
1583
		 * The dynamic portions of the hook name, `$old_status`, and `$new_status`,
1584
		 * refer to the old and new comment statuses, respectively.
1585
		 *
1586
		 * @since 2.7.0
1587
		 *
1588
		 * @param WP_Comment $comment Comment object.
1589
		 */
1590
		do_action( "comment_{$old_status}_to_{$new_status}", $comment );
1591
	}
1592
	/**
1593
	 * Fires when the status of a specific comment type is in transition.
1594
	 *
1595
	 * The dynamic portions of the hook name, `$new_status`, and `$comment->comment_type`,
1596
	 * refer to the new comment status, and the type of comment, respectively.
1597
	 *
1598
	 * Typical comment types include an empty string (standard comment), 'pingback',
1599
	 * or 'trackback'.
1600
	 *
1601
	 * @since 2.7.0
1602
	 *
1603
	 * @param int        $comment_ID The comment ID.
1604
	 * @param WP_Comment $comment    Comment object.
1605
	 */
1606
	do_action( "comment_{$new_status}_{$comment->comment_type}", $comment->comment_ID, $comment );
1607
}
1608
1609
/**
1610
 * Clear the lastcommentmodified cached value when a comment status is changed.
1611
 *
1612
 * Deletes the lastcommentmodified cache key when a comment enters or leaves
1613
 * 'approved' status.
1614
 *
1615
 * @since 4.7.0
1616
 * @access private
1617
 *
1618
 * @param string $new_status The new comment status.
1619
 * @param string $old_status The old comment status.
1620
 */
1621
function _clear_modified_cache_on_transition_comment_status( $new_status, $old_status ) {
1622
	if ( 'approved' === $new_status || 'approved' === $old_status ) {
1623
		foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
1624
			wp_cache_delete( "lastcommentmodified:$timezone", 'timeinfo' );
1625
		}
1626
	}
1627
}
1628
1629
/**
1630
 * Get current commenter's name, email, and URL.
1631
 *
1632
 * Expects cookies content to already be sanitized. User of this function might
1633
 * wish to recheck the returned array for validity.
1634
 *
1635
 * @see sanitize_comment_cookies() Use to sanitize cookies
1636
 *
1637
 * @since 2.0.4
1638
 *
1639
 * @return array Comment author, email, url respectively.
1640
 */
1641
function wp_get_current_commenter() {
1642
	// Cookies should already be sanitized.
1643
1644
	$comment_author = '';
1645
	if ( isset($_COOKIE['comment_author_'.COOKIEHASH]) )
1646
		$comment_author = $_COOKIE['comment_author_'.COOKIEHASH];
1647
1648
	$comment_author_email = '';
1649
	if ( isset($_COOKIE['comment_author_email_'.COOKIEHASH]) )
1650
		$comment_author_email = $_COOKIE['comment_author_email_'.COOKIEHASH];
1651
1652
	$comment_author_url = '';
1653
	if ( isset($_COOKIE['comment_author_url_'.COOKIEHASH]) )
1654
		$comment_author_url = $_COOKIE['comment_author_url_'.COOKIEHASH];
1655
1656
	/**
1657
	 * Filters the current commenter's name, email, and URL.
1658
	 *
1659
	 * @since 3.1.0
1660
	 *
1661
	 * @param array $comment_author_data {
1662
	 *     An array of current commenter variables.
1663
	 *
1664
	 *     @type string $comment_author       The name of the author of the comment. Default empty.
1665
	 *     @type string $comment_author_email The email address of the `$comment_author`. Default empty.
1666
	 *     @type string $comment_author_url   The URL address of the `$comment_author`. Default empty.
1667
	 * }
1668
	 */
1669
	return apply_filters( 'wp_get_current_commenter', compact('comment_author', 'comment_author_email', 'comment_author_url') );
1670
}
1671
1672
/**
1673
 * Inserts a comment into the database.
1674
 *
1675
 * @since 2.0.0
1676
 * @since 4.4.0 Introduced `$comment_meta` argument.
1677
 *
1678
 * @global wpdb $wpdb WordPress database abstraction object.
1679
 *
1680
 * @param array $commentdata {
1681
 *     Array of arguments for inserting a new comment.
1682
 *
1683
 *     @type string     $comment_agent        The HTTP user agent of the `$comment_author` when
1684
 *                                            the comment was submitted. Default empty.
1685
 *     @type int|string $comment_approved     Whether the comment has been approved. Default 1.
1686
 *     @type string     $comment_author       The name of the author of the comment. Default empty.
1687
 *     @type string     $comment_author_email The email address of the `$comment_author`. Default empty.
1688
 *     @type string     $comment_author_IP    The IP address of the `$comment_author`. Default empty.
1689
 *     @type string     $comment_author_url   The URL address of the `$comment_author`. Default empty.
1690
 *     @type string     $comment_content      The content of the comment. Default empty.
1691
 *     @type string     $comment_date         The date the comment was submitted. To set the date
1692
 *                                            manually, `$comment_date_gmt` must also be specified.
1693
 *                                            Default is the current time.
1694
 *     @type string     $comment_date_gmt     The date the comment was submitted in the GMT timezone.
1695
 *                                            Default is `$comment_date` in the site's GMT timezone.
1696
 *     @type int        $comment_karma        The karma of the comment. Default 0.
1697
 *     @type int        $comment_parent       ID of this comment's parent, if any. Default 0.
1698
 *     @type int        $comment_post_ID      ID of the post that relates to the comment, if any.
1699
 *                                            Default 0.
1700
 *     @type string     $comment_type         Comment type. Default empty.
1701
 *     @type array      $comment_meta         Optional. Array of key/value pairs to be stored in commentmeta for the
1702
 *                                            new comment.
1703
 *     @type int        $user_id              ID of the user who submitted the comment. Default 0.
1704
 * }
1705
 * @return int|false The new comment's ID on success, false on failure.
0 ignored issues
show
Should the return type not be false|WP_Comment|string|integer|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1706
 */
1707
function wp_insert_comment( $commentdata ) {
1708
	global $wpdb;
1709
	$data = wp_unslash( $commentdata );
1710
1711
	$comment_author       = ! isset( $data['comment_author'] )       ? '' : $data['comment_author'];
1712
	$comment_author_email = ! isset( $data['comment_author_email'] ) ? '' : $data['comment_author_email'];
1713
	$comment_author_url   = ! isset( $data['comment_author_url'] )   ? '' : $data['comment_author_url'];
1714
	$comment_author_IP    = ! isset( $data['comment_author_IP'] )    ? '' : $data['comment_author_IP'];
1715
1716
	$comment_date     = ! isset( $data['comment_date'] )     ? current_time( 'mysql' )            : $data['comment_date'];
1717
	$comment_date_gmt = ! isset( $data['comment_date_gmt'] ) ? get_gmt_from_date( $comment_date ) : $data['comment_date_gmt'];
1718
1719
	$comment_post_ID  = ! isset( $data['comment_post_ID'] )  ? 0  : $data['comment_post_ID'];
1720
	$comment_content  = ! isset( $data['comment_content'] )  ? '' : $data['comment_content'];
1721
	$comment_karma    = ! isset( $data['comment_karma'] )    ? 0  : $data['comment_karma'];
1722
	$comment_approved = ! isset( $data['comment_approved'] ) ? 1  : $data['comment_approved'];
1723
	$comment_agent    = ! isset( $data['comment_agent'] )    ? '' : $data['comment_agent'];
1724
	$comment_type     = ! isset( $data['comment_type'] )     ? '' : $data['comment_type'];
1725
	$comment_parent   = ! isset( $data['comment_parent'] )   ? 0  : $data['comment_parent'];
1726
1727
	$user_id  = ! isset( $data['user_id'] ) ? 0 : $data['user_id'];
1728
1729
	$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' );
1730
	if ( ! $wpdb->insert( $wpdb->comments, $compacted ) ) {
1731
		return false;
1732
	}
1733
1734
	$id = (int) $wpdb->insert_id;
1735
1736
	if ( $comment_approved == 1 ) {
1737
		wp_update_comment_count( $comment_post_ID );
1738
1739
		foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
1740
			wp_cache_delete( "lastcommentmodified:$timezone", 'timeinfo' );
1741
		}
1742
	}
1743
1744
	clean_comment_cache( $id );
1745
1746
	$comment = get_comment( $id );
1747
1748
	// If metadata is provided, store it.
1749
	if ( isset( $commentdata['comment_meta'] ) && is_array( $commentdata['comment_meta'] ) ) {
1750
		foreach ( $commentdata['comment_meta'] as $meta_key => $meta_value ) {
1751
			add_comment_meta( $comment->comment_ID, $meta_key, $meta_value, true );
1752
		}
1753
	}
1754
1755
	/**
1756
	 * Fires immediately after a comment is inserted into the database.
1757
	 *
1758
	 * @since 2.8.0
1759
	 *
1760
	 * @param int        $id      The comment ID.
1761
	 * @param WP_Comment $comment Comment object.
1762
	 */
1763
	do_action( 'wp_insert_comment', $id, $comment );
1764
1765
	return $id;
1766
}
1767
1768
/**
1769
 * Filters and sanitizes comment data.
1770
 *
1771
 * Sets the comment data 'filtered' field to true when finished. This can be
1772
 * checked as to whether the comment should be filtered and to keep from
1773
 * filtering the same comment more than once.
1774
 *
1775
 * @since 2.0.0
1776
 *
1777
 * @param array $commentdata Contains information on the comment.
1778
 * @return array Parsed comment information.
1779
 */
1780
function wp_filter_comment($commentdata) {
1781
	if ( isset( $commentdata['user_ID'] ) ) {
1782
		/**
1783
		 * Filters the comment author's user id before it is set.
1784
		 *
1785
		 * The first time this filter is evaluated, 'user_ID' is checked
1786
		 * (for back-compat), followed by the standard 'user_id' value.
1787
		 *
1788
		 * @since 1.5.0
1789
		 *
1790
		 * @param int $user_ID The comment author's user ID.
1791
		 */
1792
		$commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_ID'] );
1793
	} elseif ( isset( $commentdata['user_id'] ) ) {
1794
		/** This filter is documented in wp-includes/comment.php */
1795
		$commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_id'] );
1796
	}
1797
1798
	/**
1799
	 * Filters the comment author's browser user agent before it is set.
1800
	 *
1801
	 * @since 1.5.0
1802
	 *
1803
	 * @param string $comment_agent The comment author's browser user agent.
1804
	 */
1805
	$commentdata['comment_agent'] = apply_filters( 'pre_comment_user_agent', ( isset( $commentdata['comment_agent'] ) ? $commentdata['comment_agent'] : '' ) );
1806
	/** This filter is documented in wp-includes/comment.php */
1807
	$commentdata['comment_author'] = apply_filters( 'pre_comment_author_name', $commentdata['comment_author'] );
1808
	/**
1809
	 * Filters the comment content before it is set.
1810
	 *
1811
	 * @since 1.5.0
1812
	 *
1813
	 * @param string $comment_content The comment content.
1814
	 */
1815
	$commentdata['comment_content'] = apply_filters( 'pre_comment_content', $commentdata['comment_content'] );
1816
	/**
1817
	 * Filters the comment author's IP before it is set.
1818
	 *
1819
	 * @since 1.5.0
1820
	 *
1821
	 * @param string $comment_author_ip The comment author's IP.
1822
	 */
1823
	$commentdata['comment_author_IP'] = apply_filters( 'pre_comment_user_ip', $commentdata['comment_author_IP'] );
1824
	/** This filter is documented in wp-includes/comment.php */
1825
	$commentdata['comment_author_url'] = apply_filters( 'pre_comment_author_url', $commentdata['comment_author_url'] );
1826
	/** This filter is documented in wp-includes/comment.php */
1827
	$commentdata['comment_author_email'] = apply_filters( 'pre_comment_author_email', $commentdata['comment_author_email'] );
1828
	$commentdata['filtered'] = true;
1829
	return $commentdata;
1830
}
1831
1832
/**
1833
 * Whether a comment should be blocked because of comment flood.
1834
 *
1835
 * @since 2.1.0
1836
 *
1837
 * @param bool $block Whether plugin has already blocked comment.
1838
 * @param int $time_lastcomment Timestamp for last comment.
1839
 * @param int $time_newcomment Timestamp for new comment.
1840
 * @return bool Whether comment should be blocked.
1841
 */
1842
function wp_throttle_comment_flood($block, $time_lastcomment, $time_newcomment) {
1843
	if ( $block ) // a plugin has already blocked... we'll let that decision stand
1844
		return $block;
1845
	if ( ($time_newcomment - $time_lastcomment) < 15 )
0 ignored issues
show
This if statement, and the following return statement can be replaced with return $time_newcomment - $time_lastcomment < 15;.
Loading history...
1846
		return true;
1847
	return false;
1848
}
1849
1850
/**
1851
 * Adds a new comment to the database.
1852
 *
1853
 * Filters new comment to ensure that the fields are sanitized and valid before
1854
 * inserting comment into database. Calls {@see 'comment_post'} action with comment ID
1855
 * and whether comment is approved by WordPress. Also has {@see 'preprocess_comment'}
1856
 * filter for processing the comment data before the function handles it.
1857
 *
1858
 * We use `REMOTE_ADDR` here directly. If you are behind a proxy, you should ensure
1859
 * that it is properly set, such as in wp-config.php, for your environment.
1860
 *
1861
 * See {@link https://core.trac.wordpress.org/ticket/9235}
1862
 *
1863
 * @since 1.5.0
1864
 * @since 4.3.0 'comment_agent' and 'comment_author_IP' can be set via `$commentdata`.
1865
 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function to
1866
 *              return a WP_Error object instead of dying.
1867
 *
1868
 * @see wp_insert_comment()
1869
 * @global wpdb $wpdb WordPress database abstraction object.
1870
 *
1871
 * @param array $commentdata {
1872
 *     Comment data.
1873
 *
1874
 *     @type string $comment_author       The name of the comment author.
1875
 *     @type string $comment_author_email The comment author email address.
1876
 *     @type string $comment_author_url   The comment author URL.
1877
 *     @type string $comment_content      The content of the comment.
1878
 *     @type string $comment_date         The date the comment was submitted. Default is the current time.
1879
 *     @type string $comment_date_gmt     The date the comment was submitted in the GMT timezone.
1880
 *                                        Default is `$comment_date` in the GMT timezone.
1881
 *     @type int    $comment_parent       The ID of this comment's parent, if any. Default 0.
1882
 *     @type int    $comment_post_ID      The ID of the post that relates to the comment.
1883
 *     @type int    $user_id              The ID of the user who submitted the comment. Default 0.
1884
 *     @type int    $user_ID              Kept for backward-compatibility. Use `$user_id` instead.
1885
 *     @type string $comment_agent        Comment author user agent. Default is the value of 'HTTP_USER_AGENT'
1886
 *                                        in the `$_SERVER` superglobal sent in the original request.
1887
 *     @type string $comment_author_IP    Comment author IP address in IPv4 format. Default is the value of
1888
 *                                        'REMOTE_ADDR' in the `$_SERVER` superglobal sent in the original request.
1889
 * }
1890
 * @param bool $avoid_die Should errors be returned as WP_Error objects instead of
1891
 *                        executing wp_die()? Default false.
1892
 * @return int|false|WP_Error The ID of the comment on success, false or WP_Error on failure.
0 ignored issues
show
Should the return type not be integer|string|WP_Error|false|WP_Comment?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1893
 */
1894
function wp_new_comment( $commentdata, $avoid_die = false ) {
1895
	global $wpdb;
1896
1897
	if ( isset( $commentdata['user_ID'] ) ) {
1898
		$commentdata['user_id'] = $commentdata['user_ID'] = (int) $commentdata['user_ID'];
1899
	}
1900
1901
	$prefiltered_user_id = ( isset( $commentdata['user_id'] ) ) ? (int) $commentdata['user_id'] : 0;
1902
1903
	/**
1904
	 * Filters a comment's data before it is sanitized and inserted into the database.
1905
	 *
1906
	 * @since 1.5.0
1907
	 *
1908
	 * @param array $commentdata Comment data.
1909
	 */
1910
	$commentdata = apply_filters( 'preprocess_comment', $commentdata );
1911
1912
	$commentdata['comment_post_ID'] = (int) $commentdata['comment_post_ID'];
1913
	if ( isset( $commentdata['user_ID'] ) && $prefiltered_user_id !== (int) $commentdata['user_ID'] ) {
1914
		$commentdata['user_id'] = $commentdata['user_ID'] = (int) $commentdata['user_ID'];
1915
	} elseif ( isset( $commentdata['user_id'] ) ) {
1916
		$commentdata['user_id'] = (int) $commentdata['user_id'];
1917
	}
1918
1919
	$commentdata['comment_parent'] = isset($commentdata['comment_parent']) ? absint($commentdata['comment_parent']) : 0;
1920
	$parent_status = ( 0 < $commentdata['comment_parent'] ) ? wp_get_comment_status($commentdata['comment_parent']) : '';
1921
	$commentdata['comment_parent'] = ( 'approved' == $parent_status || 'unapproved' == $parent_status ) ? $commentdata['comment_parent'] : 0;
1922
1923
	if ( ! isset( $commentdata['comment_author_IP'] ) ) {
1924
		$commentdata['comment_author_IP'] = $_SERVER['REMOTE_ADDR'];
1925
	}
1926
	$commentdata['comment_author_IP'] = preg_replace( '/[^0-9a-fA-F:., ]/', '', $commentdata['comment_author_IP'] );
1927
1928
	if ( ! isset( $commentdata['comment_agent'] ) ) {
1929
		$commentdata['comment_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT']: '';
1930
	}
1931
	$commentdata['comment_agent'] = substr( $commentdata['comment_agent'], 0, 254 );
1932
1933
	if ( empty( $commentdata['comment_date'] ) ) {
1934
		$commentdata['comment_date'] = current_time('mysql');
1935
	}
1936
1937
	if ( empty( $commentdata['comment_date_gmt'] ) ) {
1938
		$commentdata['comment_date_gmt'] = current_time( 'mysql', 1 );
1939
	}
1940
1941
	$commentdata = wp_filter_comment($commentdata);
1942
1943
	$commentdata['comment_approved'] = wp_allow_comment( $commentdata, $avoid_die );
1944
	if ( is_wp_error( $commentdata['comment_approved'] ) ) {
1945
		return $commentdata['comment_approved'];
1946
	}
1947
1948
	$comment_ID = wp_insert_comment($commentdata);
1949
	if ( ! $comment_ID ) {
1950
		$fields = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content' );
1951
1952
		foreach ( $fields as $field ) {
1953
			if ( isset( $commentdata[ $field ] ) ) {
1954
				$commentdata[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->comments, $field, $commentdata[ $field ] );
1955
			}
1956
		}
1957
1958
		$commentdata = wp_filter_comment( $commentdata );
1959
1960
		$commentdata['comment_approved'] = wp_allow_comment( $commentdata, $avoid_die );
1961
		if ( is_wp_error( $commentdata['comment_approved'] ) ) {
1962
			return $commentdata['comment_approved'];
1963
		}
1964
1965
		$comment_ID = wp_insert_comment( $commentdata );
1966
		if ( ! $comment_ID ) {
1967
			return false;
1968
		}
1969
	}
1970
1971
	/**
1972
	 * Fires immediately after a comment is inserted into the database.
1973
	 *
1974
	 * @since 1.2.0
1975
	 * @since 4.5.0 The `$commentdata` parameter was added.
1976
	 *
1977
	 * @param int        $comment_ID       The comment ID.
1978
	 * @param int|string $comment_approved 1 if the comment is approved, 0 if not, 'spam' if spam.
1979
	 * @param array      $commentdata      Comment data.
1980
	 */
1981
	do_action( 'comment_post', $comment_ID, $commentdata['comment_approved'], $commentdata );
1982
1983
	return $comment_ID;
1984
}
1985
1986
/**
1987
 * Send a comment moderation notification to the comment moderator.
1988
 *
1989
 * @since 4.4.0
1990
 *
1991
 * @param int $comment_ID ID of the comment.
1992
 * @return bool True on success, false on failure.
1993
 */
1994
function wp_new_comment_notify_moderator( $comment_ID ) {
1995
	$comment = get_comment( $comment_ID );
1996
1997
	// Only send notifications for pending comments.
1998
	$maybe_notify = ( '0' == $comment->comment_approved );
1999
2000
	/** This filter is documented in wp-includes/comment.php */
2001
	$maybe_notify = apply_filters( 'notify_moderator', $maybe_notify, $comment_ID );
2002
2003
	if ( ! $maybe_notify ) {
2004
		return false;
2005
	}
2006
2007
	return wp_notify_moderator( $comment_ID );
2008
}
2009
2010
/**
2011
 * Send a notification of a new comment to the post author.
2012
 *
2013
 * @since 4.4.0
2014
 *
2015
 * Uses the {@see 'notify_post_author'} filter to determine whether the post author
2016
 * should be notified when a new comment is added, overriding site setting.
2017
 *
2018
 * @param int $comment_ID Comment ID.
2019
 * @return bool True on success, false on failure.
2020
 */
2021
function wp_new_comment_notify_postauthor( $comment_ID ) {
2022
	$comment = get_comment( $comment_ID );
2023
2024
	$maybe_notify = get_option( 'comments_notify' );
2025
2026
	/**
2027
	 * Filters whether to send the post author new comment notification emails,
2028
	 * overriding the site setting.
2029
	 *
2030
	 * @since 4.4.0
2031
	 *
2032
	 * @param bool $maybe_notify Whether to notify the post author about the new comment.
2033
	 * @param int  $comment_ID   The ID of the comment for the notification.
2034
	 */
2035
	$maybe_notify = apply_filters( 'notify_post_author', $maybe_notify, $comment_ID );
2036
2037
	/*
2038
	 * wp_notify_postauthor() checks if notifying the author of their own comment.
2039
	 * By default, it won't, but filters can override this.
2040
	 */
2041
	if ( ! $maybe_notify ) {
2042
		return false;
2043
	}
2044
2045
	// Only send notifications for approved comments.
2046
	if ( ! isset( $comment->comment_approved ) || '1' != $comment->comment_approved ) {
2047
		return false;
2048
	}
2049
2050
	return wp_notify_postauthor( $comment_ID );
2051
}
2052
2053
/**
2054
 * Sets the status of a comment.
2055
 *
2056
 * The {@see 'wp_set_comment_status'} action is called after the comment is handled.
2057
 * If the comment status is not in the list, then false is returned.
2058
 *
2059
 * @since 1.0.0
2060
 *
2061
 * @global wpdb $wpdb WordPress database abstraction object.
2062
 *
2063
 * @param int|WP_Comment $comment_id     Comment ID or WP_Comment object.
2064
 * @param string         $comment_status New comment status, either 'hold', 'approve', 'spam', or 'trash'.
2065
 * @param bool           $wp_error       Whether to return a WP_Error object if there is a failure. Default is false.
2066
 * @return bool|WP_Error True on success, false or WP_Error on failure.
2067
 */
2068
function wp_set_comment_status($comment_id, $comment_status, $wp_error = false) {
2069
	global $wpdb;
2070
2071
	switch ( $comment_status ) {
2072
		case 'hold':
2073
		case '0':
2074
			$status = '0';
2075
			break;
2076
		case 'approve':
2077
		case '1':
2078
			$status = '1';
2079
			add_action( 'wp_set_comment_status', 'wp_new_comment_notify_postauthor' );
2080
			break;
2081
		case 'spam':
2082
			$status = 'spam';
2083
			break;
2084
		case 'trash':
2085
			$status = 'trash';
2086
			break;
2087
		default:
2088
			return false;
2089
	}
2090
2091
	$comment_old = clone get_comment($comment_id);
2092
2093
	if ( !$wpdb->update( $wpdb->comments, array('comment_approved' => $status), array( 'comment_ID' => $comment_old->comment_ID ) ) ) {
2094
		if ( $wp_error )
2095
			return new WP_Error('db_update_error', __('Could not update comment status'), $wpdb->last_error);
2096
		else
2097
			return false;
2098
	}
2099
2100
	clean_comment_cache( $comment_old->comment_ID );
2101
2102
	$comment = get_comment( $comment_old->comment_ID );
2103
2104
	/**
2105
	 * Fires immediately before transitioning a comment's status from one to another
2106
	 * in the database.
2107
	 *
2108
	 * @since 1.5.0
2109
	 *
2110
	 * @param int         $comment_id     Comment ID.
2111
	 * @param string|bool $comment_status Current comment status. Possible values include
2112
	 *                                    'hold', 'approve', 'spam', 'trash', or false.
2113
	 */
2114
	do_action( 'wp_set_comment_status', $comment->comment_ID, $comment_status );
2115
2116
	wp_transition_comment_status($comment_status, $comment_old->comment_approved, $comment);
2117
2118
	wp_update_comment_count($comment->comment_post_ID);
2119
2120
	return true;
2121
}
2122
2123
/**
2124
 * Updates an existing comment in the database.
2125
 *
2126
 * Filters the comment and makes sure certain fields are valid before updating.
2127
 *
2128
 * @since 2.0.0
2129
 *
2130
 * @global wpdb $wpdb WordPress database abstraction object.
2131
 *
2132
 * @param array $commentarr Contains information on the comment.
2133
 * @return int Comment was updated if value is 1, or was not updated if value is 0.
2134
 */
2135
function wp_update_comment($commentarr) {
2136
	global $wpdb;
2137
2138
	// First, get all of the original fields
2139
	$comment = get_comment($commentarr['comment_ID'], ARRAY_A);
2140
	if ( empty( $comment ) ) {
2141
		return 0;
2142
	}
2143
2144
	// Make sure that the comment post ID is valid (if specified).
2145
	if ( ! empty( $commentarr['comment_post_ID'] ) && ! get_post( $commentarr['comment_post_ID'] ) ) {
2146
		return 0;
2147
	}
2148
2149
	// Escape data pulled from DB.
2150
	$comment = wp_slash($comment);
0 ignored issues
show
It seems like $comment can also be of type object<WP_Comment>; however, wp_slash() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2151
2152
	$old_status = $comment['comment_approved'];
2153
2154
	// Merge old and new fields with new fields overwriting old ones.
2155
	$commentarr = array_merge($comment, $commentarr);
2156
2157
	$commentarr = wp_filter_comment( $commentarr );
2158
2159
	// Now extract the merged array.
2160
	$data = wp_unslash( $commentarr );
2161
2162
	/**
2163
	 * Filters the comment content before it is updated in the database.
2164
	 *
2165
	 * @since 1.5.0
2166
	 *
2167
	 * @param string $comment_content The comment data.
2168
	 */
2169
	$data['comment_content'] = apply_filters( 'comment_save_pre', $data['comment_content'] );
2170
2171
	$data['comment_date_gmt'] = get_gmt_from_date( $data['comment_date'] );
2172
2173
	if ( ! isset( $data['comment_approved'] ) ) {
2174
		$data['comment_approved'] = 1;
2175
	} elseif ( 'hold' == $data['comment_approved'] ) {
2176
		$data['comment_approved'] = 0;
2177
	} elseif ( 'approve' == $data['comment_approved'] ) {
2178
		$data['comment_approved'] = 1;
2179
	}
2180
2181
	$comment_ID = $data['comment_ID'];
2182
	$comment_post_ID = $data['comment_post_ID'];
2183
2184
	/**
2185
	 * Filters the comment data immediately before it is updated in the database.
2186
	 *
2187
	 * Note: data being passed to the filter is already unslashed.
2188
	 *
2189
	 * @since 4.7.0
2190
	 *
2191
	 * @param array $data       The new, processed comment data.
2192
	 * @param array $comment    The old, unslashed comment data.
2193
	 * @param array $commentarr The new, raw comment data.
2194
	 */
2195
	$data = apply_filters( 'wp_update_comment_data', $data, $comment, $commentarr );
2196
2197
	$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' );
2198
	$data = wp_array_slice_assoc( $data, $keys );
2199
2200
	$rval = $wpdb->update( $wpdb->comments, $data, compact( 'comment_ID' ) );
2201
2202
	clean_comment_cache( $comment_ID );
2203
	wp_update_comment_count( $comment_post_ID );
2204
	/**
2205
	 * Fires immediately after a comment is updated in the database.
2206
	 *
2207
	 * The hook also fires immediately before comment status transition hooks are fired.
2208
	 *
2209
	 * @since 1.2.0
2210
	 * @since 4.6.0 Added the `$data` parameter.
2211
	 *
2212
	 * @param int   $comment_ID The comment ID.
2213
	 * @param array $data       Comment data.
2214
	 */
2215
	do_action( 'edit_comment', $comment_ID, $data );
2216
	$comment = get_comment($comment_ID);
2217
	wp_transition_comment_status($comment->comment_approved, $old_status, $comment);
2218
	return $rval;
2219
}
2220
2221
/**
2222
 * Whether to defer comment counting.
2223
 *
2224
 * When setting $defer to true, all post comment counts will not be updated
2225
 * until $defer is set to false. When $defer is set to false, then all
2226
 * previously deferred updated post comment counts will then be automatically
2227
 * updated without having to call wp_update_comment_count() after.
2228
 *
2229
 * @since 2.5.0
2230
 * @staticvar bool $_defer
2231
 *
2232
 * @param bool $defer
2233
 * @return bool
2234
 */
2235 View Code Duplication
function wp_defer_comment_counting($defer=null) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2236
	static $_defer = false;
2237
2238
	if ( is_bool($defer) ) {
2239
		$_defer = $defer;
2240
		// flush any deferred counts
2241
		if ( !$defer )
2242
			wp_update_comment_count( null, true );
2243
	}
2244
2245
	return $_defer;
2246
}
2247
2248
/**
2249
 * Updates the comment count for post(s).
2250
 *
2251
 * When $do_deferred is false (is by default) and the comments have been set to
2252
 * be deferred, the post_id will be added to a queue, which will be updated at a
2253
 * later date and only updated once per post ID.
2254
 *
2255
 * If the comments have not be set up to be deferred, then the post will be
2256
 * updated. When $do_deferred is set to true, then all previous deferred post
2257
 * IDs will be updated along with the current $post_id.
2258
 *
2259
 * @since 2.1.0
2260
 * @see wp_update_comment_count_now() For what could cause a false return value
2261
 *
2262
 * @staticvar array $_deferred
2263
 *
2264
 * @param int|null $post_id     Post ID.
2265
 * @param bool     $do_deferred Optional. Whether to process previously deferred
2266
 *                              post comment counts. Default false.
2267
 * @return bool|void True on success, false on failure or if post with ID does
2268
 *                   not exist.
2269
 */
2270
function wp_update_comment_count($post_id, $do_deferred=false) {
2271
	static $_deferred = array();
2272
2273
	if ( empty( $post_id ) && ! $do_deferred ) {
2274
		return false;
2275
	}
2276
2277
	if ( $do_deferred ) {
2278
		$_deferred = array_unique($_deferred);
2279
		foreach ( $_deferred as $i => $_post_id ) {
2280
			wp_update_comment_count_now($_post_id);
2281
			unset( $_deferred[$i] ); /** @todo Move this outside of the foreach and reset $_deferred to an array instead */
2282
		}
2283
	}
2284
2285
	if ( wp_defer_comment_counting() ) {
2286
		$_deferred[] = $post_id;
2287
		return true;
2288
	}
2289
	elseif ( $post_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $post_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2290
		return wp_update_comment_count_now($post_id);
2291
	}
2292
2293
}
2294
2295
/**
2296
 * Updates the comment count for the post.
2297
 *
2298
 * @since 2.5.0
2299
 *
2300
 * @global wpdb $wpdb WordPress database abstraction object.
2301
 *
2302
 * @param int $post_id Post ID
2303
 * @return bool True on success, false on '0' $post_id or if post with ID does not exist.
2304
 */
2305
function wp_update_comment_count_now($post_id) {
2306
	global $wpdb;
2307
	$post_id = (int) $post_id;
2308
	if ( !$post_id )
2309
		return false;
2310
2311
	wp_cache_delete( 'comments-0', 'counts' );
2312
	wp_cache_delete( "comments-{$post_id}", 'counts' );
2313
2314
	if ( !$post = get_post($post_id) )
2315
		return false;
2316
2317
	$old = (int) $post->comment_count;
2318
2319
	/**
2320
	 * Filters a post's comment count before it is updated in the database.
2321
	 *
2322
	 * @since 4.5.0
2323
	 *
2324
	 * @param int $new     The new comment count. Default null.
2325
	 * @param int $old     The old comment count.
2326
	 * @param int $post_id Post ID.
2327
	 */
2328
	$new = apply_filters( 'pre_wp_update_comment_count_now', null, $old, $post_id );
2329
2330
	if ( is_null( $new ) ) {
2331
		$new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1'", $post_id ) );
2332
	} else {
2333
		$new = (int) $new;
2334
	}
2335
2336
	$wpdb->update( $wpdb->posts, array('comment_count' => $new), array('ID' => $post_id) );
2337
2338
	clean_post_cache( $post );
2339
2340
	/**
2341
	 * Fires immediately after a post's comment count is updated in the database.
2342
	 *
2343
	 * @since 2.3.0
2344
	 *
2345
	 * @param int $post_id Post ID.
2346
	 * @param int $new     The new comment count.
2347
	 * @param int $old     The old comment count.
2348
	 */
2349
	do_action( 'wp_update_comment_count', $post_id, $new, $old );
2350
	/** This action is documented in wp-includes/post.php */
2351
	do_action( 'edit_post', $post_id, $post );
2352
2353
	return true;
2354
}
2355
2356
//
2357
// Ping and trackback functions.
2358
//
2359
2360
/**
2361
 * Finds a pingback server URI based on the given URL.
2362
 *
2363
 * Checks the HTML for the rel="pingback" link and x-pingback headers. It does
2364
 * a check for the x-pingback headers first and returns that, if available. The
2365
 * check for the rel="pingback" has more overhead than just the header.
2366
 *
2367
 * @since 1.5.0
2368
 *
2369
 * @param string $url URL to ping.
2370
 * @param int $deprecated Not Used.
2371
 * @return false|string False on failure, string containing URI on success.
2372
 */
2373
function discover_pingback_server_uri( $url, $deprecated = '' ) {
2374
	if ( !empty( $deprecated ) )
2375
		_deprecated_argument( __FUNCTION__, '2.7.0' );
2376
2377
	$pingback_str_dquote = 'rel="pingback"';
2378
	$pingback_str_squote = 'rel=\'pingback\'';
2379
2380
	/** @todo Should use Filter Extension or custom preg_match instead. */
2381
	$parsed_url = parse_url($url);
2382
2383
	if ( ! isset( $parsed_url['host'] ) ) // Not a URL. This should never happen.
2384
		return false;
2385
2386
	//Do not search for a pingback server on our own uploads
2387
	$uploads_dir = wp_get_upload_dir();
2388
	if ( 0 === strpos($url, $uploads_dir['baseurl']) )
2389
		return false;
2390
2391
	$response = wp_safe_remote_head( $url, array( 'timeout' => 2, 'httpversion' => '1.0' ) );
2392
2393
	if ( is_wp_error( $response ) )
2394
		return false;
2395
2396
	if ( wp_remote_retrieve_header( $response, 'x-pingback' ) )
2397
		return wp_remote_retrieve_header( $response, 'x-pingback' );
2398
2399
	// Not an (x)html, sgml, or xml page, no use going further.
2400
	if ( preg_match('#(image|audio|video|model)/#is', wp_remote_retrieve_header( $response, 'content-type' )) )
2401
		return false;
2402
2403
	// Now do a GET since we're going to look in the html headers (and we're sure it's not a binary file)
2404
	$response = wp_safe_remote_get( $url, array( 'timeout' => 2, 'httpversion' => '1.0' ) );
2405
2406
	if ( is_wp_error( $response ) )
2407
		return false;
2408
2409
	$contents = wp_remote_retrieve_body( $response );
2410
2411
	$pingback_link_offset_dquote = strpos($contents, $pingback_str_dquote);
2412
	$pingback_link_offset_squote = strpos($contents, $pingback_str_squote);
2413
	if ( $pingback_link_offset_dquote || $pingback_link_offset_squote ) {
2414
		$quote = ($pingback_link_offset_dquote) ? '"' : '\'';
2415
		$pingback_link_offset = ($quote=='"') ? $pingback_link_offset_dquote : $pingback_link_offset_squote;
2416
		$pingback_href_pos = @strpos($contents, 'href=', $pingback_link_offset);
2417
		$pingback_href_start = $pingback_href_pos+6;
2418
		$pingback_href_end = @strpos($contents, $quote, $pingback_href_start);
2419
		$pingback_server_url_len = $pingback_href_end - $pingback_href_start;
2420
		$pingback_server_url = substr($contents, $pingback_href_start, $pingback_server_url_len);
2421
2422
		// We may find rel="pingback" but an incomplete pingback URL
2423
		if ( $pingback_server_url_len > 0 ) { // We got it!
2424
			return $pingback_server_url;
2425
		}
2426
	}
2427
2428
	return false;
2429
}
2430
2431
/**
2432
 * Perform all pingbacks, enclosures, trackbacks, and send to pingback services.
2433
 *
2434
 * @since 2.1.0
2435
 *
2436
 * @global wpdb $wpdb WordPress database abstraction object.
2437
 */
2438
function do_all_pings() {
2439
	global $wpdb;
2440
2441
	// Do pingbacks
2442 View Code Duplication
	while ($ping = $wpdb->get_row("SELECT ID, post_content, meta_id FROM {$wpdb->posts}, {$wpdb->postmeta} WHERE {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_pingme' LIMIT 1")) {
2443
		delete_metadata_by_mid( 'post', $ping->meta_id );
2444
		pingback( $ping->post_content, $ping->ID );
2445
	}
2446
2447
	// Do Enclosures
2448 View Code Duplication
	while ($enclosure = $wpdb->get_row("SELECT ID, post_content, meta_id FROM {$wpdb->posts}, {$wpdb->postmeta} WHERE {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_encloseme' LIMIT 1")) {
2449
		delete_metadata_by_mid( 'post', $enclosure->meta_id );
2450
		do_enclose( $enclosure->post_content, $enclosure->ID );
2451
	}
2452
2453
	// Do Trackbacks
2454
	$trackbacks = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE to_ping <> '' AND post_status = 'publish'");
2455
	if ( is_array($trackbacks) )
2456
		foreach ( $trackbacks as $trackback )
2457
			do_trackbacks($trackback);
2458
2459
	//Do Update Services/Generic Pings
2460
	generic_ping();
2461
}
2462
2463
/**
2464
 * Perform trackbacks.
2465
 *
2466
 * @since 1.5.0
2467
 * @since 4.7.0 $post_id can be a WP_Post object.
2468
 *
2469
 * @global wpdb $wpdb WordPress database abstraction object.
2470
 *
2471
 * @param int|WP_Post $post_id Post object or ID to do trackbacks on.
2472
 */
2473
function do_trackbacks( $post_id ) {
2474
	global $wpdb;
2475
	$post = get_post( $post_id );
2476
	if ( ! $post ) {
2477
		return false;
2478
	}
2479
2480
	$to_ping = get_to_ping( $post );
2481
	$pinged  = get_pung( $post );
2482
	if ( empty( $to_ping ) ) {
2483
		$wpdb->update($wpdb->posts, array( 'to_ping' => '' ), array( 'ID' => $post->ID ) );
2484
		return;
2485
	}
2486
2487
	if ( empty($post->post_excerpt) ) {
2488
		/** This filter is documented in wp-includes/post-template.php */
2489
		$excerpt = apply_filters( 'the_content', $post->post_content, $post->ID );
2490
	} else {
2491
		/** This filter is documented in wp-includes/post-template.php */
2492
		$excerpt = apply_filters( 'the_excerpt', $post->post_excerpt );
2493
	}
2494
2495
	$excerpt = str_replace(']]>', ']]&gt;', $excerpt);
2496
	$excerpt = wp_html_excerpt($excerpt, 252, '&#8230;');
2497
2498
	/** This filter is documented in wp-includes/post-template.php */
2499
	$post_title = apply_filters( 'the_title', $post->post_title, $post->ID );
2500
	$post_title = strip_tags($post_title);
2501
2502
	if ( $to_ping ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $to_ping of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2503
		foreach ( (array) $to_ping as $tb_ping ) {
2504
			$tb_ping = trim($tb_ping);
2505
			if ( !in_array($tb_ping, $pinged) ) {
2506
				trackback( $tb_ping, $post_title, $excerpt, $post->ID );
2507
				$pinged[] = $tb_ping;
2508
			} else {
2509
				$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s,
2510
					'')) WHERE ID = %d", $tb_ping, $post->ID ) );
2511
			}
2512
		}
2513
	}
2514
}
2515
2516
/**
2517
 * Sends pings to all of the ping site services.
2518
 *
2519
 * @since 1.2.0
2520
 *
2521
 * @param int $post_id Post ID.
2522
 * @return int Same as Post ID from parameter
2523
 */
2524
function generic_ping( $post_id = 0 ) {
2525
	$services = get_option('ping_sites');
2526
2527
	$services = explode("\n", $services);
2528
	foreach ( (array) $services as $service ) {
2529
		$service = trim($service);
2530
		if ( '' != $service )
2531
			weblog_ping($service);
2532
	}
2533
2534
	return $post_id;
2535
}
2536
2537
/**
2538
 * Pings back the links found in a post.
2539
 *
2540
 * @since 0.71
2541
 * @since 4.7.0 $post_id can be a WP_Post object.
2542
 *
2543
 * @param string $content Post content to check for links. If empty will retrieve from post.
2544
 * @param int|WP_Post $post_id Post Object or ID.
2545
 */
2546
function pingback( $content, $post_id ) {
2547
	include_once( ABSPATH . WPINC . '/class-IXR.php' );
2548
	include_once( ABSPATH . WPINC . '/class-wp-http-ixr-client.php' );
2549
2550
	// original code by Mort (http://mort.mine.nu:8080)
2551
	$post_links = array();
2552
2553
	$post = get_post( $post_id );
2554
	if ( ! $post ) {
2555
		return;
2556
	}
2557
2558
	$pung = get_pung( $post );
2559
2560
	if ( empty( $content ) ) {
2561
		$content = $post->post_content;
2562
	}
2563
2564
	// Step 1
2565
	// Parsing the post, external links (if any) are stored in the $post_links array
2566
	$post_links_temp = wp_extract_urls( $content );
2567
2568
	// Step 2.
2569
	// Walking thru the links array
2570
	// first we get rid of links pointing to sites, not to specific files
2571
	// Example:
2572
	// http://dummy-weblog.org
2573
	// http://dummy-weblog.org/
2574
	// http://dummy-weblog.org/post.php
2575
	// We don't wanna ping first and second types, even if they have a valid <link/>
2576
2577
	foreach ( (array) $post_links_temp as $link_test ) :
2578
		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
2579
				&& !is_local_attachment($link_test) ) : // Also, let's never ping local attachments.
2580
			if ( $test = @parse_url($link_test) ) {
2581 View Code Duplication
				if ( isset($test['query']) )
2582
					$post_links[] = $link_test;
2583
				elseif ( isset( $test['path'] ) && ( $test['path'] != '/' ) && ( $test['path'] != '' ) )
2584
					$post_links[] = $link_test;
2585
			}
2586
		endif;
2587
	endforeach;
2588
2589
	$post_links = array_unique( $post_links );
2590
	/**
2591
	 * Fires just before pinging back links found in a post.
2592
	 *
2593
	 * @since 2.0.0
2594
	 *
2595
	 * @param array &$post_links An array of post links to be checked, passed by reference.
2596
	 * @param array &$pung       Whether a link has already been pinged, passed by reference.
2597
	 * @param int   $post_ID     The post ID.
2598
	 */
2599
	do_action_ref_array( 'pre_ping', array( &$post_links, &$pung, $post->ID ) );
2600
2601
	foreach ( (array) $post_links as $pagelinkedto ) {
2602
		$pingback_server_url = discover_pingback_server_uri( $pagelinkedto );
2603
2604
		if ( $pingback_server_url ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pingback_server_url of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2605
			@ set_time_limit( 60 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2606
			// Now, the RPC call
2607
			$pagelinkedfrom = get_permalink( $post );
2608
2609
			// using a timeout of 3 seconds should be enough to cover slow servers
2610
			$client = new WP_HTTP_IXR_Client($pingback_server_url);
2611
			$client->timeout = 3;
2612
			/**
2613
			 * Filters the user agent sent when pinging-back a URL.
2614
			 *
2615
			 * @since 2.9.0
2616
			 *
2617
			 * @param string $concat_useragent    The user agent concatenated with ' -- WordPress/'
2618
			 *                                    and the WordPress version.
2619
			 * @param string $useragent           The useragent.
2620
			 * @param string $pingback_server_url The server URL being linked to.
2621
			 * @param string $pagelinkedto        URL of page linked to.
2622
			 * @param string $pagelinkedfrom      URL of page linked from.
2623
			 */
2624
			$client->useragent = apply_filters( 'pingback_useragent', $client->useragent . ' -- WordPress/' . get_bloginfo( 'version' ), $client->useragent, $pingback_server_url, $pagelinkedto, $pagelinkedfrom );
2625
			// when set to true, this outputs debug messages by itself
2626
			$client->debug = false;
2627
2628
			if ( $client->query('pingback.ping', $pagelinkedfrom, $pagelinkedto) || ( isset($client->error->code) && 48 == $client->error->code ) ) // Already registered
2629
				add_ping( $post, $pagelinkedto );
2630
		}
2631
	}
2632
}
2633
2634
/**
2635
 * Check whether blog is public before returning sites.
2636
 *
2637
 * @since 2.1.0
2638
 *
2639
 * @param mixed $sites Will return if blog is public, will not return if not public.
2640
 * @return mixed Empty string if blog is not public, returns $sites, if site is public.
2641
 */
2642
function privacy_ping_filter($sites) {
2643
	if ( '0' != get_option('blog_public') )
2644
		return $sites;
2645
	else
2646
		return '';
2647
}
2648
2649
/**
2650
 * Send a Trackback.
2651
 *
2652
 * Updates database when sending trackback to prevent duplicates.
2653
 *
2654
 * @since 0.71
2655
 *
2656
 * @global wpdb $wpdb WordPress database abstraction object.
2657
 *
2658
 * @param string $trackback_url URL to send trackbacks.
2659
 * @param string $title Title of post.
2660
 * @param string $excerpt Excerpt of post.
2661
 * @param int $ID Post ID.
2662
 * @return int|false|void Database query from update.
2663
 */
2664
function trackback($trackback_url, $title, $excerpt, $ID) {
2665
	global $wpdb;
2666
2667
	if ( empty($trackback_url) )
2668
		return;
2669
2670
	$options = array();
2671
	$options['timeout'] = 10;
2672
	$options['body'] = array(
2673
		'title' => $title,
2674
		'url' => get_permalink($ID),
2675
		'blog_name' => get_option('blogname'),
2676
		'excerpt' => $excerpt
2677
	);
2678
2679
	$response = wp_safe_remote_post( $trackback_url, $options );
2680
2681
	if ( is_wp_error( $response ) )
2682
		return;
2683
2684
	$wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', %s) WHERE ID = %d", $trackback_url, $ID) );
2685
	return $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $trackback_url, $ID) );
2686
}
2687
2688
/**
2689
 * Send a pingback.
2690
 *
2691
 * @since 1.2.0
2692
 *
2693
 * @param string $server Host of blog to connect to.
2694
 * @param string $path Path to send the ping.
2695
 */
2696
function weblog_ping($server = '', $path = '') {
2697
	include_once( ABSPATH . WPINC . '/class-IXR.php' );
2698
	include_once( ABSPATH . WPINC . '/class-wp-http-ixr-client.php' );
2699
2700
	// using a timeout of 3 seconds should be enough to cover slow servers
2701
	$client = new WP_HTTP_IXR_Client($server, ((!strlen(trim($path)) || ('/' == $path)) ? false : $path));
2702
	$client->timeout = 3;
2703
	$client->useragent .= ' -- WordPress/' . get_bloginfo( 'version' );
2704
2705
	// when set to true, this outputs debug messages by itself
2706
	$client->debug = false;
2707
	$home = trailingslashit( home_url() );
2708
	if ( !$client->query('weblogUpdates.extendedPing', get_option('blogname'), $home, get_bloginfo('rss2_url') ) ) // then try a normal ping
2709
		$client->query('weblogUpdates.ping', get_option('blogname'), $home);
2710
}
2711
2712
/**
2713
 * Default filter attached to pingback_ping_source_uri to validate the pingback's Source URI
2714
 *
2715
 * @since 3.5.1
2716
 * @see wp_http_validate_url()
2717
 *
2718
 * @param string $source_uri
2719
 * @return string
2720
 */
2721
function pingback_ping_source_uri( $source_uri ) {
2722
	return (string) wp_http_validate_url( $source_uri );
2723
}
2724
2725
/**
2726
 * Default filter attached to xmlrpc_pingback_error.
2727
 *
2728
 * Returns a generic pingback error code unless the error code is 48,
2729
 * which reports that the pingback is already registered.
2730
 *
2731
 * @since 3.5.1
2732
 * @link https://www.hixie.ch/specs/pingback/pingback#TOC3
2733
 *
2734
 * @param IXR_Error $ixr_error
2735
 * @return IXR_Error
2736
 */
2737
function xmlrpc_pingback_error( $ixr_error ) {
2738
	if ( $ixr_error->code === 48 )
2739
		return $ixr_error;
2740
	return new IXR_Error( 0, '' );
2741
}
2742
2743
//
2744
// Cache
2745
//
2746
2747
/**
2748
 * Removes a comment from the object cache.
2749
 *
2750
 * @since 2.3.0
2751
 *
2752
 * @param int|array $ids Comment ID or an array of comment IDs to remove from cache.
2753
 */
2754
function clean_comment_cache($ids) {
2755
	foreach ( (array) $ids as $id ) {
2756
		wp_cache_delete( $id, 'comment' );
2757
2758
		/**
2759
		 * Fires immediately after a comment has been removed from the object cache.
2760
		 *
2761
		 * @since 4.5.0
2762
		 *
2763
		 * @param int $id Comment ID.
2764
		 */
2765
		do_action( 'clean_comment_cache', $id );
2766
	}
2767
2768
	wp_cache_set( 'last_changed', microtime(), 'comment' );
2769
}
2770
2771
/**
2772
 * Updates the comment cache of given comments.
2773
 *
2774
 * Will add the comments in $comments to the cache. If comment ID already exists
2775
 * in the comment cache then it will not be updated. The comment is added to the
2776
 * cache using the comment group with the key using the ID of the comments.
2777
 *
2778
 * @since 2.3.0
2779
 * @since 4.4.0 Introduced the `$update_meta_cache` parameter.
2780
 *
2781
 * @param array $comments          Array of comment row objects
2782
 * @param bool  $update_meta_cache Whether to update commentmeta cache. Default true.
2783
 */
2784
function update_comment_cache( $comments, $update_meta_cache = true ) {
2785
	foreach ( (array) $comments as $comment )
2786
		wp_cache_add($comment->comment_ID, $comment, 'comment');
2787
2788
	if ( $update_meta_cache ) {
2789
		// Avoid `wp_list_pluck()` in case `$comments` is passed by reference.
2790
		$comment_ids = array();
2791
		foreach ( $comments as $comment ) {
2792
			$comment_ids[] = $comment->comment_ID;
2793
		}
2794
		update_meta_cache( 'comment', $comment_ids );
2795
	}
2796
}
2797
2798
/**
2799
 * Adds any comments from the given IDs to the cache that do not already exist in cache.
2800
 *
2801
 * @since 4.4.0
2802
 * @access private
2803
 *
2804
 * @see update_comment_cache()
2805
 * @global wpdb $wpdb WordPress database abstraction object.
2806
 *
2807
 * @param array $comment_ids       Array of comment IDs.
2808
 * @param bool  $update_meta_cache Optional. Whether to update the meta cache. Default true.
2809
 */
2810 View Code Duplication
function _prime_comment_caches( $comment_ids, $update_meta_cache = true ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2811
	global $wpdb;
2812
2813
	$non_cached_ids = _get_non_cached_ids( $comment_ids, 'comment' );
2814
	if ( !empty( $non_cached_ids ) ) {
2815
		$fresh_comments = $wpdb->get_results( sprintf( "SELECT $wpdb->comments.* FROM $wpdb->comments WHERE comment_ID IN (%s)", join( ",", array_map( 'intval', $non_cached_ids ) ) ) );
2816
2817
		update_comment_cache( $fresh_comments, $update_meta_cache );
2818
	}
2819
}
2820
2821
//
2822
// Internal
2823
//
2824
2825
/**
2826
 * Close comments on old posts on the fly, without any extra DB queries. Hooked to the_posts.
2827
 *
2828
 * @access private
2829
 * @since 2.7.0
2830
 *
2831
 * @param WP_Post  $posts Post data object.
2832
 * @param WP_Query $query Query object.
2833
 * @return array
0 ignored issues
show
Should the return type not be WP_Post?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2834
 */
2835
function _close_comments_for_old_posts( $posts, $query ) {
2836
	if ( empty( $posts ) || ! $query->is_singular() || ! get_option( 'close_comments_for_old_posts' ) )
2837
		return $posts;
2838
2839
	/**
2840
	 * Filters the list of post types to automatically close comments for.
2841
	 *
2842
	 * @since 3.2.0
2843
	 *
2844
	 * @param array $post_types An array of registered post types. Default array with 'post'.
2845
	 */
2846
	$post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) );
2847
	if ( ! in_array( $posts[0]->post_type, $post_types ) )
2848
		return $posts;
2849
2850
	$days_old = (int) get_option( 'close_comments_days_old' );
2851
	if ( ! $days_old )
2852
		return $posts;
2853
2854
	if ( time() - strtotime( $posts[0]->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) ) {
2855
		$posts[0]->comment_status = 'closed';
2856
		$posts[0]->ping_status = 'closed';
2857
	}
2858
2859
	return $posts;
2860
}
2861
2862
/**
2863
 * Close comments on an old post. Hooked to comments_open and pings_open.
2864
 *
2865
 * @access private
2866
 * @since 2.7.0
2867
 *
2868
 * @param bool $open Comments open or closed
2869
 * @param int $post_id Post ID
2870
 * @return bool $open
2871
 */
2872
function _close_comments_for_old_post( $open, $post_id ) {
2873
	if ( ! $open )
2874
		return $open;
2875
2876
	if ( !get_option('close_comments_for_old_posts') )
2877
		return $open;
2878
2879
	$days_old = (int) get_option('close_comments_days_old');
2880
	if ( !$days_old )
2881
		return $open;
2882
2883
	$post = get_post($post_id);
2884
2885
	/** This filter is documented in wp-includes/comment.php */
2886
	$post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) );
2887
	if ( ! in_array( $post->post_type, $post_types ) )
2888
		return $open;
2889
2890
	// Undated drafts should not show up as comments closed.
2891
	if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) {
2892
		return $open;
2893
	}
2894
2895
	if ( time() - strtotime( $post->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) )
2896
		return false;
2897
2898
	return $open;
2899
}
2900
2901
/**
2902
 * Handles the submission of a comment, usually posted to wp-comments-post.php via a comment form.
2903
 *
2904
 * This function expects unslashed data, as opposed to functions such as `wp_new_comment()` which
2905
 * expect slashed data.
2906
 *
2907
 * @since 4.4.0
2908
 *
2909
 * @param array $comment_data {
2910
 *     Comment data.
2911
 *
2912
 *     @type string|int $comment_post_ID             The ID of the post that relates to the comment.
2913
 *     @type string     $author                      The name of the comment author.
2914
 *     @type string     $email                       The comment author email address.
2915
 *     @type string     $url                         The comment author URL.
2916
 *     @type string     $comment                     The content of the comment.
2917
 *     @type string|int $comment_parent              The ID of this comment's parent, if any. Default 0.
2918
 *     @type string     $_wp_unfiltered_html_comment The nonce value for allowing unfiltered HTML.
2919
 * }
2920
 * @return WP_Comment|WP_Error A WP_Comment object on success, a WP_Error object on failure.
0 ignored issues
show
Should the return type not be WP_Error|boolean|integer...g|WP_Comment|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
2921
 */
2922
function wp_handle_comment_submission( $comment_data ) {
2923
2924
	$comment_post_ID = $comment_parent = 0;
2925
	$comment_author = $comment_author_email = $comment_author_url = $comment_content = null;
2926
2927
	if ( isset( $comment_data['comment_post_ID'] ) ) {
2928
		$comment_post_ID = (int) $comment_data['comment_post_ID'];
2929
	}
2930
	if ( isset( $comment_data['author'] ) && is_string( $comment_data['author'] ) ) {
2931
		$comment_author = trim( strip_tags( $comment_data['author'] ) );
2932
	}
2933
	if ( isset( $comment_data['email'] ) && is_string( $comment_data['email'] ) ) {
2934
		$comment_author_email = trim( $comment_data['email'] );
2935
	}
2936
	if ( isset( $comment_data['url'] ) && is_string( $comment_data['url'] ) ) {
2937
		$comment_author_url = trim( $comment_data['url'] );
2938
	}
2939
	if ( isset( $comment_data['comment'] ) && is_string( $comment_data['comment'] ) ) {
2940
		$comment_content = trim( $comment_data['comment'] );
2941
	}
2942
	if ( isset( $comment_data['comment_parent'] ) ) {
2943
		$comment_parent = absint( $comment_data['comment_parent'] );
2944
	}
2945
2946
	$post = get_post( $comment_post_ID );
2947
2948
	if ( empty( $post->comment_status ) ) {
2949
2950
		/**
2951
		 * Fires when a comment is attempted on a post that does not exist.
2952
		 *
2953
		 * @since 1.5.0
2954
		 *
2955
		 * @param int $comment_post_ID Post ID.
2956
		 */
2957
		do_action( 'comment_id_not_found', $comment_post_ID );
2958
2959
		return new WP_Error( 'comment_id_not_found' );
2960
2961
	}
2962
2963
	// get_post_status() will get the parent status for attachments.
2964
	$status = get_post_status( $post );
2965
2966
	if ( ( 'private' == $status ) && ! current_user_can( 'read_post', $comment_post_ID ) ) {
2967
		return new WP_Error( 'comment_id_not_found' );
2968
	}
2969
2970
	$status_obj = get_post_status_object( $status );
0 ignored issues
show
It seems like $status defined by get_post_status($post) on line 2964 can also be of type false; however, get_post_status_object() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2971
2972
	if ( ! comments_open( $comment_post_ID ) ) {
2973
2974
		/**
2975
		 * Fires when a comment is attempted on a post that has comments closed.
2976
		 *
2977
		 * @since 1.5.0
2978
		 *
2979
		 * @param int $comment_post_ID Post ID.
2980
		 */
2981
		do_action( 'comment_closed', $comment_post_ID );
2982
2983
		return new WP_Error( 'comment_closed', __( 'Sorry, comments are closed for this item.' ), 403 );
2984
2985
	} elseif ( 'trash' == $status ) {
2986
2987
		/**
2988
		 * Fires when a comment is attempted on a trashed post.
2989
		 *
2990
		 * @since 2.9.0
2991
		 *
2992
		 * @param int $comment_post_ID Post ID.
2993
		 */
2994
		do_action( 'comment_on_trash', $comment_post_ID );
2995
2996
		return new WP_Error( 'comment_on_trash' );
2997
2998
	} elseif ( ! $status_obj->public && ! $status_obj->private ) {
2999
3000
		/**
3001
		 * Fires when a comment is attempted on a post in draft mode.
3002
		 *
3003
		 * @since 1.5.1
3004
		 *
3005
		 * @param int $comment_post_ID Post ID.
3006
		 */
3007
		do_action( 'comment_on_draft', $comment_post_ID );
3008
		
3009
		if ( current_user_can( 'read_post', $comment_post_ID ) ) {
3010
			return new WP_Error( 'comment_on_draft', __( 'Sorry, comments are not allowed for this item.' ), 403 );
3011
		} else {
3012
			return new WP_Error( 'comment_on_draft' );
3013
		}
3014
3015
	} elseif ( post_password_required( $comment_post_ID ) ) {
3016
3017
		/**
3018
		 * Fires when a comment is attempted on a password-protected post.
3019
		 *
3020
		 * @since 2.9.0
3021
		 *
3022
		 * @param int $comment_post_ID Post ID.
3023
		 */
3024
		do_action( 'comment_on_password_protected', $comment_post_ID );
3025
3026
		return new WP_Error( 'comment_on_password_protected' );
3027
3028
	} else {
3029
3030
		/**
3031
		 * Fires before a comment is posted.
3032
		 *
3033
		 * @since 2.8.0
3034
		 *
3035
		 * @param int $comment_post_ID Post ID.
3036
		 */
3037
		do_action( 'pre_comment_on_post', $comment_post_ID );
3038
3039
	}
3040
3041
	// If the user is logged in
3042
	$user = wp_get_current_user();
3043
	if ( $user->exists() ) {
3044
		if ( empty( $user->display_name ) ) {
3045
			$user->display_name=$user->user_login;
3046
		}
3047
		$comment_author       = $user->display_name;
3048
		$comment_author_email = $user->user_email;
3049
		$comment_author_url   = $user->user_url;
3050
		$user_ID              = $user->ID;
3051
		if ( current_user_can( 'unfiltered_html' ) ) {
3052
			if ( ! isset( $comment_data['_wp_unfiltered_html_comment'] )
3053
				|| ! wp_verify_nonce( $comment_data['_wp_unfiltered_html_comment'], 'unfiltered-html-comment_' . $comment_post_ID )
3054
			) {
3055
				kses_remove_filters(); // start with a clean slate
3056
				kses_init_filters(); // set up the filters
3057
			}
3058
		}
3059
	} else {
3060
		if ( get_option( 'comment_registration' ) ) {
3061
			return new WP_Error( 'not_logged_in', __( 'Sorry, you must be logged in to comment.' ), 403 );
3062
		}
3063
	}
3064
3065
	$comment_type = '';
3066
3067
	if ( get_option( 'require_name_email' ) && ! $user->exists() ) {
3068
		if ( '' == $comment_author_email || '' == $comment_author ) {
3069
			return new WP_Error( 'require_name_email', __( '<strong>ERROR</strong>: please fill the required fields (name, email).' ), 200 );
3070
		} elseif ( ! is_email( $comment_author_email ) ) {
3071
			return new WP_Error( 'require_valid_email', __( '<strong>ERROR</strong>: please enter a valid email address.' ), 200 );
3072
		}
3073
	}
3074
3075
	if ( '' == $comment_content ) {
3076
		return new WP_Error( 'require_valid_comment', __( '<strong>ERROR</strong>: please type a comment.' ), 200 );
3077
	}
3078
3079
	$commentdata = compact(
3080
		'comment_post_ID',
3081
		'comment_author',
3082
		'comment_author_email',
3083
		'comment_author_url',
3084
		'comment_content',
3085
		'comment_type',
3086
		'comment_parent',
3087
		'user_ID'
3088
	);
3089
3090
	$check_max_lengths = wp_check_comment_data_max_lengths( $commentdata );
3091
	if ( is_wp_error( $check_max_lengths ) ) {
3092
		return $check_max_lengths;
3093
	}
3094
3095
	$comment_id = wp_new_comment( wp_slash( $commentdata ), true );
0 ignored issues
show
It seems like wp_slash($commentdata) targeting wp_slash() can also be of type string; however, wp_new_comment() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3096
	if ( is_wp_error( $comment_id ) ) {
3097
		return $comment_id;
3098
	}
3099
3100
	if ( ! $comment_id ) {
3101
		return new WP_Error( 'comment_save_error', __( '<strong>ERROR</strong>: The comment could not be saved. Please try again later.' ), 500 );
3102
	}
3103
3104
	return get_comment( $comment_id );
3105
}
3106