Issues (2010)

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.

wp-includes/comment.php (19 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

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