run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄comment.php
1<?php
2/**
3 * Core Comment API
4 *
5 * @package WordPress
6 * @subpackage Comment
7 */
8
9/**
10 * Checks 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 substance, 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 contain any disallowed words,
18 * then the check fails.
19 *
20 * If the comment author was approved before, then the comment is automatically
21 * approved.
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 */
39function check_comment( $author, $email, $url, $comment, $user_ip, $user_agent, $comment_type ) {
40 global $wpdb;
41
42 // If manual moderation is enabled, skip all checks and return false.
43 if ( '1' === get_option( 'comment_moderation' ) ) {
44 return false;
45 }
46
47 /** This filter is documented in wp-includes/comment-template.php */
48 $comment = apply_filters( 'comment_text', $comment, null, array() );
49
50 // Check for the number of external links if a max allowed number is set.
51 $max_links = get_option( 'comment_max_links' );
52 if ( $max_links ) {
53 $num_links = preg_match_all( '/<a [^>]*href/i', $comment, $out );
54
55 /**
56 * Filters the number of links found in a comment.
57 *
58 * @since 3.0.0
59 * @since 4.7.0 Added the `$comment` parameter.
60 *
61 * @param int $num_links The number of links found.
62 * @param string $url Comment author's URL. Included in allowed links total.
63 * @param string $comment Content of the comment.
64 */
65 $num_links = apply_filters( 'comment_max_links_url', $num_links, $url, $comment );
66
67 /*
68 * If the number of links in the comment exceeds the allowed amount,
69 * fail the check by returning false.
70 */
71 if ( $num_links >= $max_links ) {
72 return false;
73 }
74 }
75
76 $mod_keys = trim( get_option( 'moderation_keys' ) );
77
78 // If moderation 'keys' (keywords) are set, process them.
79 if ( ! empty( $mod_keys ) ) {
80 $words = explode( "\n", $mod_keys );
81
82 foreach ( (array) $words as $word ) {
83 $word = trim( $word );
84
85 // Skip empty lines.
86 if ( empty( $word ) ) {
87 continue;
88 }
89
90 /*
91 * Do some escaping magic so that '#' (number of) characters in the spam
92 * words don't break things:
93 */
94 $word = preg_quote( $word, '#' );
95
96 /*
97 * Check the comment fields for moderation keywords. If any are found,
98 * fail the check for the given field by returning false.
99 */
100 $pattern = "#$word#iu";
101 if ( preg_match( $pattern, $author ) ) {
102 return false;
103 }
104 if ( preg_match( $pattern, $email ) ) {
105 return false;
106 }
107 if ( preg_match( $pattern, $url ) ) {
108 return false;
109 }
110 if ( preg_match( $pattern, $comment ) ) {
111 return false;
112 }
113 if ( preg_match( $pattern, $user_ip ) ) {
114 return false;
115 }
116 if ( preg_match( $pattern, $user_agent ) ) {
117 return false;
118 }
119 }
120 }
121
122 /*
123 * Check if the option to approve comments by previously-approved authors is enabled.
124 *
125 * If it is enabled, check whether the comment author has a previously-approved comment,
126 * as well as whether there are any moderation keywords (if set) present in the author
127 * email address. If both checks pass, return true. Otherwise, return false.
128 */
129 if ( '1' === get_option( 'comment_previously_approved' ) ) {
130 if ( 'trackback' !== $comment_type && 'pingback' !== $comment_type && '' !== $author && '' !== $email ) {
131 $comment_user = get_user_by( 'email', wp_unslash( $email ) );
132 if ( ! empty( $comment_user->ID ) ) {
133 $ok_to_comment = $wpdb->get_var(
134 $wpdb->prepare(
135 "SELECT comment_approved
136 FROM $wpdb->comments
137 WHERE user_id = %d
138 AND comment_approved = '1'
139 LIMIT 1",
140 $comment_user->ID
141 )
142 );
143 } else {
144 // expected_slashed ($author, $email)
145 $ok_to_comment = $wpdb->get_var(
146 $wpdb->prepare(
147 "SELECT comment_approved
148 FROM $wpdb->comments
149 WHERE comment_author = %s
150 AND comment_author_email = %s
151 AND comment_approved = '1'
152 LIMIT 1",
153 $author,
154 $email
155 )
156 );
157 }
158
159 if ( '1' === $ok_to_comment && ( empty( $mod_keys ) || ! str_contains( $email, $mod_keys ) ) ) {
160 return true;
161 } else {
162 return false;
163 }
164 } else {
165 return false;
166 }
167 }
168 return true;
169}
170
171/**
172 * Retrieves the approved comments for a post.
173 *
174 * @since 2.0.0
175 * @since 4.1.0 Refactored to leverage WP_Comment_Query over a direct query.
176 *
177 * @param int $post_id The ID of the post.
178 * @param array $args {
179 * Optional. See WP_Comment_Query::__construct() for information on accepted arguments.
180 *
181 * @type int $status Comment status to limit results by. Defaults to approved comments.
182 * @type int $post_id Limit results to those affiliated with a given post ID.
183 * @type string $order How to order retrieved comments. Default 'ASC'.
184 * }
185 * @return WP_Comment[]|int[]|int The approved comments, or number of comments if `$count`
186 * argument is true.
187 */
188function get_approved_comments( $post_id, $args = array() ) {
189 if ( ! $post_id ) {
190 return array();
191 }
192
193 $defaults = array(
194 'status' => 1,
195 'post_id' => $post_id,
196 'order' => 'ASC',
197 );
198 $parsed_args = wp_parse_args( $args, $defaults );
199
200 $query = new WP_Comment_Query();
201 return $query->query( $parsed_args );
202}
203
204/**
205 * Retrieves comment data given a comment ID or comment object.
206 *
207 * If an object is passed then the comment data will be cached and then returned
208 * after being passed through a filter. If the comment is empty, then the global
209 * comment variable will be used, if it is set.
210 *
211 * @since 2.0.0
212 *
213 * @global WP_Comment $comment Global comment object.
214 *
215 * @param WP_Comment|string|int $comment Comment to retrieve.
216 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
217 * correspond to a WP_Comment object, an associative array, or a numeric array,
218 * respectively. Default OBJECT.
219 * @return WP_Comment|array|null Depends on $output value.
220 */
221function get_comment( $comment = null, $output = OBJECT ) {
222 if ( empty( $comment ) && isset( $GLOBALS['comment'] ) ) {
223 $comment = $GLOBALS['comment'];
224 }
225
226 if ( $comment instanceof WP_Comment ) {
227 $_comment = $comment;
228 } elseif ( is_object( $comment ) ) {
229 $_comment = new WP_Comment( $comment );
230 } else {
231 $_comment = WP_Comment::get_instance( $comment );
232 }
233
234 if ( ! $_comment ) {
235 return null;
236 }
237
238 /**
239 * Fires after a comment is retrieved.
240 *
241 * @since 2.3.0
242 *
243 * @param WP_Comment $_comment Comment data.
244 */
245 $_comment = apply_filters( 'get_comment', $_comment );
246
247 if ( OBJECT === $output ) {
248 return $_comment;
249 } elseif ( ARRAY_A === $output ) {
250 return $_comment->to_array();
251 } elseif ( ARRAY_N === $output ) {
252 return array_values( $_comment->to_array() );
253 }
254 return $_comment;
255}
256
257/**
258 * Retrieves a list of comments.
259 *
260 * The comment list can be for the blog as a whole or for an individual post.
261 *
262 * @since 2.7.0
263 *
264 * @param string|array $args Optional. Array or string of arguments. See WP_Comment_Query::__construct()
265 * for information on accepted arguments. Default empty string.
266 * @return WP_Comment[]|int[]|int List of comments or number of found comments if `$count` argument is true.
267 */
268function get_comments( $args = '' ) {
269 $query = new WP_Comment_Query();
270 return $query->query( $args );
271}
272
273/**
274 * Retrieves all of the WordPress supported comment statuses.
275 *
276 * Comments have a limited set of valid status values, this provides the comment
277 * status values and descriptions.
278 *
279 * @since 2.7.0
280 *
281 * @return string[] List of comment status labels keyed by status.
282 */
283function get_comment_statuses() {
284 $status = array(
285 'hold' => __( 'Unapproved' ),
286 'approve' => _x( 'Approved', 'comment status' ),
287 'spam' => _x( 'Spam', 'comment status' ),
288 'trash' => _x( 'Trash', 'comment status' ),
289 );
290
291 return $status;
292}
293
294/**
295 * Gets the default comment status for a post type.
296 *
297 * @since 4.3.0
298 *
299 * @param string $post_type Optional. Post type. Default 'post'.
300 * @param string $comment_type Optional. Comment type. Default 'comment'.
301 * @return string Either 'open' or 'closed'.
302 */
303function get_default_comment_status( $post_type = 'post', $comment_type = 'comment' ) {
304 switch ( $comment_type ) {
305 case 'pingback':
306 case 'trackback':
307 $supports = 'trackbacks';
308 $option = 'ping';
309 break;
310 default:
311 $supports = 'comments';
312 $option = 'comment';
313 break;
314 }
315
316 // Set the status.
317 if ( 'page' === $post_type ) {
318 $status = 'closed';
319 } elseif ( post_type_supports( $post_type, $supports ) ) {
320 $status = get_option( "default_{$option}_status" );
321 } else {
322 $status = 'closed';
323 }
324
325 /**
326 * Filters the default comment status for the given post type.
327 *
328 * @since 4.3.0
329 *
330 * @param string $status Default status for the given post type,
331 * either 'open' or 'closed'.
332 * @param string $post_type Post type. Default is `post`.
333 * @param string $comment_type Type of comment. Default is `comment`.
334 */
335 return apply_filters( 'get_default_comment_status', $status, $post_type, $comment_type );
336}
337
338/**
339 * Retrieves the date the last comment was modified.
340 *
341 * @since 1.5.0
342 * @since 4.7.0 Replaced caching the modified date in a local static variable
343 * with the Object Cache API.
344 *
345 * @global wpdb $wpdb WordPress database abstraction object.
346 *
347 * @param string $timezone Which timezone to use in reference to 'gmt', 'blog', or 'server' locations.
348 * @return string|false Last comment modified date on success, false on failure.
349 */
350function get_lastcommentmodified( $timezone = 'server' ) {
351 global $wpdb;
352
353 $timezone = strtolower( $timezone );
354 $key = "lastcommentmodified:$timezone";
355
356 $comment_modified_date = wp_cache_get( $key, 'timeinfo' );
357 if ( false !== $comment_modified_date ) {
358 return $comment_modified_date;
359 }
360
361 switch ( $timezone ) {
362 case 'gmt':
363 $comment_modified_date = $wpdb->get_var( "SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
364 break;
365 case 'blog':
366 $comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
367 break;
368 case 'server':
369 $add_seconds_server = gmdate( 'Z' );
370
371 $comment_modified_date = $wpdb->get_var( $wpdb->prepare( "SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server ) );
372 break;
373 }
374
375 if ( $comment_modified_date ) {
376 wp_cache_set( $key, $comment_modified_date, 'timeinfo' );
377
378 return $comment_modified_date;
379 }
380
381 return false;
382}
383
384/**
385 * Retrieves the total comment counts for the whole site or a single post.
386 *
387 * @since 2.0.0
388 *
389 * @param int $post_id Optional. Restrict the comment counts to the given post. Default 0, which indicates that
390 * comment counts for the whole site will be retrieved.
391 * @return int[] {
392 * The number of comments keyed by their status.
393 *
394 * @type int $approved The number of approved comments.
395 * @type int $awaiting_moderation The number of comments awaiting moderation (a.k.a. pending).
396 * @type int $spam The number of spam comments.
397 * @type int $trash The number of trashed comments.
398 * @type int $post-trashed The number of comments for posts that are in the trash.
399 * @type int $total_comments The total number of non-trashed comments, including spam.
400 * @type int $all The total number of pending or approved comments.
401 * }
402 */
403function get_comment_count( $post_id = 0 ) {
404 $post_id = (int) $post_id;
405
406 $comment_count = array(
407 'approved' => 0,
408 'awaiting_moderation' => 0,
409 'spam' => 0,
410 'trash' => 0,
411 'post-trashed' => 0,
412 'total_comments' => 0,
413 'all' => 0,
414 );
415
416 $args = array(
417 'count' => true,
418 'update_comment_meta_cache' => false,
419 'orderby' => 'none',
420 );
421 if ( $post_id > 0 ) {
422 $args['post_id'] = $post_id;
423 }
424 $mapping = array(
425 'approved' => 'approve',
426 'awaiting_moderation' => 'hold',
427 'spam' => 'spam',
428 'trash' => 'trash',
429 'post-trashed' => 'post-trashed',
430 );
431 $comment_count = array();
432 foreach ( $mapping as $key => $value ) {
433 $comment_count[ $key ] = get_comments( array_merge( $args, array( 'status' => $value ) ) );
434 }
435
436 $comment_count['all'] = $comment_count['approved'] + $comment_count['awaiting_moderation'];
437 $comment_count['total_comments'] = $comment_count['all'] + $comment_count['spam'];
438
439 return array_map( 'intval', $comment_count );
440}
441
442//
443// Comment meta functions.
444//
445
446/**
447 * Adds meta data field to a comment.
448 *
449 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
450 *
451 * @since 2.9.0
452 *
453 * @link https://developer.wordpress.org/reference/functions/add_comment_meta/
454 *
455 * @param int $comment_id Comment ID.
456 * @param string $meta_key Metadata name.
457 * @param mixed $meta_value Metadata value. Arrays and objects are stored as serialized data and
458 * will be returned as the same type when retrieved. Other data types will
459 * be stored as strings in the database:
460 * - false is stored and retrieved as an empty string ('')
461 * - true is stored and retrieved as '1'
462 * - numbers (both integer and float) are stored and retrieved as strings
463 * Must be serializable if non-scalar.
464 * @param bool $unique Optional. Whether the same key should not be added.
465 * Default false.
466 * @return int|false Meta ID on success, false on failure.
467 */
468function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false ) {
469 return add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique );
470}
471
472/**
473 * Removes metadata matching criteria from a comment.
474 *
475 * You can match based on the key, or key and value. Removing based on key and
476 * value, will keep from removing duplicate metadata with the same key. It also
477 * allows removing all metadata matching key, if needed.
478 *
479 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
480 *
481 * @since 2.9.0
482 *
483 * @link https://developer.wordpress.org/reference/functions/delete_comment_meta/
484 *
485 * @param int $comment_id Comment ID.
486 * @param string $meta_key Metadata name.
487 * @param mixed $meta_value Optional. Metadata value. If provided,
488 * rows will only be removed that match the value.
489 * Must be serializable if non-scalar. Default empty string.
490 * @return bool True on success, false on failure.
491 */
492function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
493 return delete_metadata( 'comment', $comment_id, $meta_key, $meta_value );
494}
495
496/**
497 * Retrieves comment meta field for a comment.
498 *
499 * @since 2.9.0
500 *
501 * @link https://developer.wordpress.org/reference/functions/get_comment_meta/
502 *
503 * @param int $comment_id Comment ID.
504 * @param string $key Optional. The meta key to retrieve. By default,
505 * returns data for all keys. Default empty string.
506 * @param bool $single Optional. Whether to return a single value.
507 * This parameter has no effect if `$key` is not specified.
508 * Default false.
509 * @return mixed An array of values if `$single` is false.
510 * The value of meta data field if `$single` is true.
511 * False for an invalid `$comment_id` (non-numeric, zero, or negative value).
512 * An empty array if a valid but non-existing comment ID is passed and `$single` is false.
513 * An empty string if a valid but non-existing comment ID is passed and `$single` is true.
514 * Note: Non-serialized values are returned as strings:
515 * - false values are returned as empty strings ('')
516 * - true values are returned as '1'
517 * - numbers are returned as strings
518 * Arrays and objects retain their original type.
519 */
520function get_comment_meta( $comment_id, $key = '', $single = false ) {
521 return get_metadata( 'comment', $comment_id, $key, $single );
522}
523
524/**
525 * Queue comment meta for lazy-loading.
526 *
527 * @since 6.3.0
528 *
529 * @param array $comment_ids List of comment IDs.
530 */
531function wp_lazyload_comment_meta( array $comment_ids ) {
532 if ( empty( $comment_ids ) ) {
533 return;
534 }
535 $lazyloader = wp_metadata_lazyloader();
536 $lazyloader->queue_objects( 'comment', $comment_ids );
537}
538
539/**
540 * Updates comment meta field based on comment ID.
541 *
542 * Use the $prev_value parameter to differentiate between meta fields with the
543 * same key and comment ID.
544 *
545 * If the meta field for the comment does not exist, it will be added.
546 *
547 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
548 *
549 * @since 2.9.0
550 *
551 * @link https://developer.wordpress.org/reference/functions/update_comment_meta/
552 *
553 * @param int $comment_id Comment ID.
554 * @param string $meta_key Metadata key.
555 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
556 * @param mixed $prev_value Optional. Previous value to check before updating.
557 * If specified, only update existing metadata entries with
558 * this value. Otherwise, update all entries. Default empty string.
559 * @return int|bool Meta ID if the key didn't exist, true on successful update,
560 * false on failure or if the value passed to the function
561 * is the same as the one that is already in the database.
562 */
563function update_comment_meta( $comment_id, $meta_key, $meta_value, $prev_value = '' ) {
564 return update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
565}
566
567/**
568 * Sets the cookies used to store an unauthenticated commentator's identity. Typically used
569 * to recall previous comments by this commentator that are still held in moderation.
570 *
571 * @since 3.4.0
572 * @since 4.9.6 The `$cookies_consent` parameter was added.
573 *
574 * @param WP_Comment $comment Comment object.
575 * @param WP_User $user Comment author's user object. The user may not exist.
576 * @param bool $cookies_consent Optional. Comment author's consent to store cookies. Default true.
577 */
578function wp_set_comment_cookies( $comment, $user, $cookies_consent = true ) {
579 // If the user already exists, or the user opted out of cookies, don't set cookies.
580 if ( $user->exists() ) {
581 return;
582 }
583
584 if ( false === $cookies_consent ) {
585 // Remove any existing cookies.
586 $past = time() - YEAR_IN_SECONDS;
587 setcookie( 'comment_author_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
588 setcookie( 'comment_author_email_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
589 setcookie( 'comment_author_url_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN );
590
591 return;
592 }
593
594 /**
595 * Filters the lifetime of the comment cookie in seconds.
596 *
597 * @since 2.8.0
598 * @since 6.6.0 The default $seconds value changed from 30000000 to YEAR_IN_SECONDS.
599 *
600 * @param int $seconds Comment cookie lifetime. Default YEAR_IN_SECONDS.
601 */
602 $comment_cookie_lifetime = time() + apply_filters( 'comment_cookie_lifetime', YEAR_IN_SECONDS );
603
604 $secure = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
605
606 setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
607 setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
608 setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure );
609}
610
611/**
612 * Sanitizes the cookies sent to the user already.
613 *
614 * Will only do anything if the cookies have already been created for the user.
615 * Mostly used after cookies had been sent to use elsewhere.
616 *
617 * @since 2.0.4
618 */
619function sanitize_comment_cookies() {
620 if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
621 /**
622 * Filters the comment author's name cookie before it is set.
623 *
624 * When this filter hook is evaluated in wp_filter_comment(),
625 * the comment author's name string is passed.
626 *
627 * @since 1.5.0
628 *
629 * @param string $author_cookie The comment author name cookie.
630 */
631 $comment_author = apply_filters( 'pre_comment_author_name', $_COOKIE[ 'comment_author_' . COOKIEHASH ] );
632 $comment_author = wp_unslash( $comment_author );
633 $comment_author = esc_attr( $comment_author );
634
635 $_COOKIE[ 'comment_author_' . COOKIEHASH ] = $comment_author;
636 }
637
638 if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
639 /**
640 * Filters the comment author's email cookie before it is set.
641 *
642 * When this filter hook is evaluated in wp_filter_comment(),
643 * the comment author's email string is passed.
644 *
645 * @since 1.5.0
646 *
647 * @param string $author_email_cookie The comment author email cookie.
648 */
649 $comment_author_email = apply_filters( 'pre_comment_author_email', $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] );
650 $comment_author_email = wp_unslash( $comment_author_email );
651 $comment_author_email = esc_attr( $comment_author_email );
652
653 $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] = $comment_author_email;
654 }
655
656 if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
657 /**
658 * Filters the comment author's URL cookie before it is set.
659 *
660 * When this filter hook is evaluated in wp_filter_comment(),
661 * the comment author's URL string is passed.
662 *
663 * @since 1.5.0
664 *
665 * @param string $author_url_cookie The comment author URL cookie.
666 */
667 $comment_author_url = apply_filters( 'pre_comment_author_url', $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] );
668 $comment_author_url = wp_unslash( $comment_author_url );
669
670 $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] = $comment_author_url;
671 }
672}
673
674/**
675 * Validates whether this comment is allowed to be made.
676 *
677 * @since 2.0.0
678 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function
679 * to return a WP_Error object instead of dying.
680 * @since 5.5.0 The `$avoid_die` parameter was renamed to `$wp_error`.
681 *
682 * @global wpdb $wpdb WordPress database abstraction object.
683 *
684 * @param array $commentdata Contains information on the comment.
685 * @param bool $wp_error When true, a disallowed comment will result in the function
686 * returning a WP_Error object, rather than executing wp_die().
687 * Default false.
688 * @return int|string|WP_Error Allowed comments return the approval status (0|1|'spam'|'trash').
689 * If `$wp_error` is true, disallowed comments return a WP_Error.
690 */
691function wp_allow_comment( $commentdata, $wp_error = false ) {
692 global $wpdb;
693
694 /*
695 * Simple duplicate check.
696 * expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
697 */
698 $dupe = $wpdb->prepare(
699 "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_parent = %s AND comment_approved != 'trash' AND ( comment_author = %s ",
700 wp_unslash( $commentdata['comment_post_ID'] ),
701 wp_unslash( $commentdata['comment_parent'] ),
702 wp_unslash( $commentdata['comment_author'] )
703 );
704 if ( $commentdata['comment_author_email'] ) {
705 $dupe .= $wpdb->prepare(
706 'AND comment_author_email = %s ',
707 wp_unslash( $commentdata['comment_author_email'] )
708 );
709 }
710 $dupe .= $wpdb->prepare(
711 ') AND comment_content = %s LIMIT 1',
712 wp_unslash( $commentdata['comment_content'] )
713 );
714
715 $dupe_id = $wpdb->get_var( $dupe );
716
717 /**
718 * Filters the ID, if any, of the duplicate comment found when creating a new comment.
719 *
720 * Return an empty value from this filter to allow what WP considers a duplicate comment.
721 *
722 * @since 4.4.0
723 *
724 * @param int $dupe_id ID of the comment identified as a duplicate.
725 * @param array $commentdata Data for the comment being created.
726 */
727 $dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata );
728
729 if ( $dupe_id ) {
730 /**
731 * Fires immediately after a duplicate comment is detected.
732 *
733 * @since 3.0.0
734 *
735 * @param array $commentdata Comment data.
736 */
737 do_action( 'comment_duplicate_trigger', $commentdata );
738
739 /**
740 * Filters duplicate comment error message.
741 *
742 * @since 5.2.0
743 *
744 * @param string $comment_duplicate_message Duplicate comment error message.
745 */
746 $comment_duplicate_message = apply_filters( 'comment_duplicate_message', __( 'Duplicate comment detected; it looks as though you&#8217;ve already said that!' ) );
747
748 if ( $wp_error ) {
749 return new WP_Error( 'comment_duplicate', $comment_duplicate_message, 409 );
750 } else {
751 if ( wp_doing_ajax() ) {
752 die( $comment_duplicate_message );
753 }
754
755 wp_die( $comment_duplicate_message, 409 );
756 }
757 }
758
759 /**
760 * Fires immediately before a comment is marked approved.
761 *
762 * Allows checking for comment flooding.
763 *
764 * @since 2.3.0
765 * @since 4.7.0 The `$avoid_die` parameter was added.
766 * @since 5.5.0 The `$avoid_die` parameter was renamed to `$wp_error`.
767 *
768 * @param string $comment_author_ip Comment author's IP address.
769 * @param string $comment_author_email Comment author's email.
770 * @param string $comment_date_gmt GMT date the comment was posted.
771 * @param bool $wp_error Whether to return a WP_Error object instead of executing
772 * wp_die() or die() if a comment flood is occurring.
773 */
774 do_action(
775 'check_comment_flood',
776 $commentdata['comment_author_IP'],
777 $commentdata['comment_author_email'],
778 $commentdata['comment_date_gmt'],
779 $wp_error
780 );
781
782 /**
783 * Filters whether a comment is part of a comment flood.
784 *
785 * The default check is wp_check_comment_flood(). See check_comment_flood_db().
786 *
787 * @since 4.7.0
788 * @since 5.5.0 The `$avoid_die` parameter was renamed to `$wp_error`.
789 *
790 * @param bool $is_flood Is a comment flooding occurring? Default false.
791 * @param string $comment_author_ip Comment author's IP address.
792 * @param string $comment_author_email Comment author's email.
793 * @param string $comment_date_gmt GMT date the comment was posted.
794 * @param bool $wp_error Whether to return a WP_Error object instead of executing
795 * wp_die() or die() if a comment flood is occurring.
796 */
797 $is_flood = apply_filters(
798 'wp_is_comment_flood',
799 false,
800 $commentdata['comment_author_IP'],
801 $commentdata['comment_author_email'],
802 $commentdata['comment_date_gmt'],
803 $wp_error
804 );
805
806 if ( $is_flood ) {
807 /** This filter is documented in wp-includes/comment-template.php */
808 $comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );
809
810 return new WP_Error( 'comment_flood', $comment_flood_message, 429 );
811 }
812
813 return wp_check_comment_data( $commentdata );
814}
815
816/**
817 * Hooks WP's native database-based comment-flood check.
818 *
819 * This wrapper maintains backward compatibility with plugins that expect to
820 * be able to unhook the legacy check_comment_flood_db() function from
821 * 'check_comment_flood' using remove_action().
822 *
823 * @since 2.3.0
824 * @since 4.7.0 Converted to be an add_filter() wrapper.
825 */
826function check_comment_flood_db() {
827 add_filter( 'wp_is_comment_flood', 'wp_check_comment_flood', 10, 5 );
828}
829
830/**
831 * Checks whether comment flooding is occurring.
832 *
833 * Won't run, if current user can manage options, so to not block
834 * administrators.
835 *
836 * @since 4.7.0
837 *
838 * @global wpdb $wpdb WordPress database abstraction object.
839 *
840 * @param bool $is_flood Is a comment flooding occurring?
841 * @param string $ip Comment author's IP address.
842 * @param string $email Comment author's email address.
843 * @param string $date MySQL time string.
844 * @param bool $avoid_die When true, a disallowed comment will result in the function
845 * returning without executing wp_die() or die(). Default false.
846 * @return bool Whether comment flooding is occurring.
847 */
848function wp_check_comment_flood( $is_flood, $ip, $email, $date, $avoid_die = false ) {
849 global $wpdb;
850
851 // Another callback has declared a flood. Trust it.
852 if ( true === $is_flood ) {
853 return $is_flood;
854 }
855
856 // Don't throttle admins or moderators.
857 if ( current_user_can( 'manage_options' ) || current_user_can( 'moderate_comments' ) ) {
858 return false;
859 }
860
861 $hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
862
863 if ( is_user_logged_in() ) {
864 $user = get_current_user_id();
865 $check_column = '`user_id`';
866 } else {
867 $user = $ip;
868 $check_column = '`comment_author_IP`';
869 }
870
871 $sql = $wpdb->prepare(
872 "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",
873 $hour_ago,
874 $user,
875 $email
876 );
877
878 $lasttime = $wpdb->get_var( $sql );
879
880 if ( $lasttime ) {
881 $time_lastcomment = mysql2date( 'U', $lasttime, false );
882 $time_newcomment = mysql2date( 'U', $date, false );
883
884 /**
885 * Filters the comment flood status.
886 *
887 * @since 2.1.0
888 *
889 * @param bool $bool Whether a comment flood is occurring. Default false.
890 * @param int $time_lastcomment Timestamp of when the last comment was posted.
891 * @param int $time_newcomment Timestamp of when the new comment was posted.
892 */
893 $flood_die = apply_filters( 'comment_flood_filter', false, $time_lastcomment, $time_newcomment );
894
895 if ( $flood_die ) {
896 /**
897 * Fires before the comment flood message is triggered.
898 *
899 * @since 1.5.0
900 *
901 * @param int $time_lastcomment Timestamp of when the last comment was posted.
902 * @param int $time_newcomment Timestamp of when the new comment was posted.
903 */
904 do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
905
906 if ( $avoid_die ) {
907 return true;
908 } else {
909 /**
910 * Filters the comment flood error message.
911 *
912 * @since 5.2.0
913 *
914 * @param string $comment_flood_message Comment flood error message.
915 */
916 $comment_flood_message = apply_filters( 'comment_flood_message', __( 'You are posting comments too quickly. Slow down.' ) );
917
918 if ( wp_doing_ajax() ) {
919 die( $comment_flood_message );
920 }
921
922 wp_die( $comment_flood_message, 429 );
923 }
924 }
925 }
926
927 return false;
928}
929
930/**
931 * Separates an array of comments into an array keyed by comment_type.
932 *
933 * @since 2.7.0
934 *
935 * @param WP_Comment[] $comments Array of comments.
936 * @return array<string, WP_Comment[]> Array of comments keyed by comment type.
937 */
938function separate_comments( &$comments ) {
939 $comments_by_type = array(
940 'comment' => array(),
941 'trackback' => array(),
942 'pingback' => array(),
943 'pings' => array(),
944 );
945
946 $count = count( $comments );
947
948 for ( $i = 0; $i < $count; $i++ ) {
949 $type = $comments[ $i ]->comment_type;
950
951 if ( empty( $type ) ) {
952 $type = 'comment';
953 }
954
955 $comments_by_type[ $type ][] = &$comments[ $i ];
956
957 if ( 'trackback' === $type || 'pingback' === $type ) {
958 $comments_by_type['pings'][] = &$comments[ $i ];
959 }
960 }
961
962 return $comments_by_type;
963}
964
965/**
966 * Calculates the total number of comment pages.
967 *
968 * @since 2.7.0
969 *
970 * @uses Walker_Comment
971 *
972 * @global WP_Query $wp_query WordPress Query object.
973 *
974 * @param WP_Comment[] $comments Optional. Array of WP_Comment objects. Defaults to `$wp_query->comments`.
975 * @param int $per_page Optional. Comments per page. Defaults to the value of `comments_per_page`
976 * query var, option of the same name, or 1 (in that order).
977 * @param bool $threaded Optional. Control over flat or threaded comments. Defaults to the value
978 * of `thread_comments` option.
979 * @return int Number of comment pages.
980 */
981function get_comment_pages_count( $comments = null, $per_page = null, $threaded = null ) {
982 global $wp_query;
983
984 if ( null === $comments && null === $per_page && null === $threaded && ! empty( $wp_query->max_num_comment_pages ) ) {
985 return $wp_query->max_num_comment_pages;
986 }
987
988 if ( ( ! $comments || ! is_array( $comments ) ) && ! empty( $wp_query->comments ) ) {
989 $comments = $wp_query->comments;
990 }
991
992 if ( empty( $comments ) ) {
993 return 0;
994 }
995
996 if ( ! get_option( 'page_comments' ) ) {
997 return 1;
998 }
999
1000 if ( ! isset( $per_page ) ) {
1001 $per_page = (int) get_query_var( 'comments_per_page' );
1002 }
1003 if ( 0 === $per_page ) {
1004 $per_page = (int) get_option( 'comments_per_page' );
1005 }
1006 if ( 0 === $per_page ) {
1007 return 1;
1008 }
1009
1010 if ( ! isset( $threaded ) ) {
1011 $threaded = get_option( 'thread_comments' );
1012 }
1013
1014 if ( $threaded ) {
1015 $walker = new Walker_Comment();
1016 $count = ceil( $walker->get_number_of_root_elements( $comments ) / $per_page );
1017 } else {
1018 $count = ceil( count( $comments ) / $per_page );
1019 }
1020
1021 return (int) $count;
1022}
1023
1024/**
1025 * Calculates what page number a comment will appear on for comment paging.
1026 *
1027 * @since 2.7.0
1028 *
1029 * @global wpdb $wpdb WordPress database abstraction object.
1030 *
1031 * @param int $comment_id Comment ID.
1032 * @param array $args {
1033 * Array of optional arguments.
1034 *
1035 * @type string $type Limit paginated comments to those matching a given type.
1036 * Accepts 'comment', 'trackback', 'pingback', 'pings'
1037 * (trackbacks and pingbacks), or 'all'. Default 'all'.
1038 * @type int $per_page Per-page count to use when calculating pagination.
1039 * Defaults to the value of the 'comments_per_page' option.
1040 * @type int|string $max_depth If greater than 1, comment page will be determined
1041 * for the top-level parent `$comment_id`.
1042 * Defaults to the value of the 'thread_comments_depth' option.
1043 * }
1044 * @return int|null Comment page number or null on error.
1045 */
1046function get_page_of_comment( $comment_id, $args = array() ) {
1047 global $wpdb;
1048
1049 $page = null;
1050
1051 $comment = get_comment( $comment_id );
1052 if ( ! $comment ) {
1053 return null;
1054 }
1055
1056 $defaults = array(
1057 'type' => 'all',
1058 'page' => '',
1059 'per_page' => '',
1060 'max_depth' => '',
1061 );
1062 $args = wp_parse_args( $args, $defaults );
1063 $original_args = $args;
1064
1065 // Order of precedence: 1. `$args['per_page']`, 2. 'comments_per_page' query_var, 3. 'comments_per_page' option.
1066 if ( get_option( 'page_comments' ) ) {
1067 if ( '' === $args['per_page'] ) {
1068 $args['per_page'] = get_query_var( 'comments_per_page' );
1069 }
1070
1071 if ( '' === $args['per_page'] ) {
1072 $args['per_page'] = get_option( 'comments_per_page' );
1073 }
1074 }
1075
1076 if ( empty( $args['per_page'] ) ) {
1077 $args['per_page'] = 0;
1078 $args['page'] = 0;
1079 }
1080
1081 if ( $args['per_page'] < 1 ) {
1082 $page = 1;
1083 }
1084
1085 if ( null === $page ) {
1086 if ( '' === $args['max_depth'] ) {
1087 if ( get_option( 'thread_comments' ) ) {
1088 $args['max_depth'] = get_option( 'thread_comments_depth' );
1089 } else {
1090 $args['max_depth'] = -1;
1091 }
1092 }
1093
1094 // Find this comment's top-level parent if threading is enabled.
1095 if ( $args['max_depth'] > 1 && '0' !== $comment->comment_parent ) {
1096 return get_page_of_comment( $comment->comment_parent, $args );
1097 }
1098
1099 $comment_args = array(
1100 'type' => $args['type'],
1101 'post_id' => $comment->comment_post_ID,
1102 'fields' => 'ids',
1103 'count' => true,
1104 'status' => 'approve',
1105 'orderby' => 'none',
1106 'parent' => 0,
1107 'date_query' => array(
1108 array(
1109 'column' => "$wpdb->comments.comment_date_gmt",
1110 'before' => $comment->comment_date_gmt,
1111 ),
1112 ),
1113 );
1114
1115 if ( is_user_logged_in() ) {
1116 $comment_args['include_unapproved'] = array( get_current_user_id() );
1117 } else {
1118 $unapproved_email = wp_get_unapproved_comment_author_email();
1119
1120 if ( $unapproved_email ) {
1121 $comment_args['include_unapproved'] = array( $unapproved_email );
1122 }
1123 }
1124
1125 /**
1126 * Filters the arguments used to query comments in get_page_of_comment().
1127 *
1128 * @since 5.5.0
1129 *
1130 * @see WP_Comment_Query::__construct()
1131 *
1132 * @param array $comment_args {
1133 * Array of WP_Comment_Query arguments.
1134 *
1135 * @type string $type Limit paginated comments to those matching a given type.
1136 * Accepts 'comment', 'trackback', 'pingback', 'pings'
1137 * (trackbacks and pingbacks), or 'all'. Default 'all'.
1138 * @type int $post_id ID of the post.
1139 * @type string $fields Comment fields to return.
1140 * @type bool $count Whether to return a comment count (true) or array
1141 * of comment objects (false).
1142 * @type string $status Comment status.
1143 * @type int $parent Parent ID of comment to retrieve children of.
1144 * @type array $date_query Date query clauses to limit comments by. See WP_Date_Query.
1145 * @type array $include_unapproved Array of IDs or email addresses whose unapproved comments
1146 * will be included in paginated comments.
1147 * }
1148 */
1149 $comment_args = apply_filters( 'get_page_of_comment_query_args', $comment_args );
1150
1151 $comment_query = new WP_Comment_Query();
1152 $older_comment_count = $comment_query->query( $comment_args );
1153
1154 // No older comments? Then it's page #1.
1155 if ( 0 === $older_comment_count ) {
1156 $page = 1;
1157
1158 // Divide comments older than this one by comments per page to get this comment's page number.
1159 } else {
1160 $page = (int) ceil( ( $older_comment_count + 1 ) / $args['per_page'] );
1161 }
1162 }
1163
1164 /**
1165 * Filters the calculated page on which a comment appears.
1166 *
1167 * @since 4.4.0
1168 * @since 4.7.0 Introduced the `$comment_id` parameter.
1169 *
1170 * @param int $page Comment page.
1171 * @param array $args {
1172 * Arguments used to calculate pagination. These include arguments auto-detected by the function,
1173 * based on query vars, system settings, etc. For pristine arguments passed to the function,
1174 * see `$original_args`.
1175 *
1176 * @type string $type Type of comments to count.
1177 * @type int $page Calculated current page.
1178 * @type int $per_page Calculated number of comments per page.
1179 * @type int $max_depth Maximum comment threading depth allowed.
1180 * }
1181 * @param array $original_args {
1182 * Array of arguments passed to the function. Some or all of these may not be set.
1183 *
1184 * @type string $type Type of comments to count.
1185 * @type int $page Current comment page.
1186 * @type int $per_page Number of comments per page.
1187 * @type int $max_depth Maximum comment threading depth allowed.
1188 * }
1189 * @param int $comment_id ID of the comment.
1190 */
1191 return apply_filters( 'get_page_of_comment', (int) $page, $args, $original_args, $comment_id );
1192}
1193
1194/**
1195 * Retrieves the maximum character lengths for the comment form fields.
1196 *
1197 * @since 4.5.0
1198 *
1199 * @global wpdb $wpdb WordPress database abstraction object.
1200 *
1201 * @return int[] Array of maximum lengths keyed by field name.
1202 */
1203function wp_get_comment_fields_max_lengths() {
1204 global $wpdb;
1205
1206 $lengths = array(
1207 'comment_author' => 245,
1208 'comment_author_email' => 100,
1209 'comment_author_url' => 200,
1210 'comment_content' => 65525,
1211 );
1212
1213 if ( $wpdb->is_mysql ) {
1214 foreach ( $lengths as $column => $length ) {
1215 $col_length = $wpdb->get_col_length( $wpdb->comments, $column );
1216 $max_length = 0;
1217
1218 // No point if we can't get the DB column lengths.
1219 if ( is_wp_error( $col_length ) ) {
1220 break;
1221 }
1222
1223 if ( ! is_array( $col_length ) && (int) $col_length > 0 ) {
1224 $max_length = (int) $col_length;
1225 } elseif ( is_array( $col_length ) && isset( $col_length['length'] ) && (int) $col_length['length'] > 0 ) {
1226 $max_length = (int) $col_length['length'];
1227
1228 if ( ! empty( $col_length['type'] ) && 'byte' === $col_length['type'] ) {
1229 $max_length = $max_length - 10;
1230 }
1231 }
1232
1233 if ( $max_length > 0 ) {
1234 $lengths[ $column ] = $max_length;
1235 }
1236 }
1237 }
1238
1239 /**
1240 * Filters the lengths for the comment form fields.
1241 *
1242 * @since 4.5.0
1243 *
1244 * @param int[] $lengths Array of maximum lengths keyed by field name.
1245 */
1246 return apply_filters( 'wp_get_comment_fields_max_lengths', $lengths );
1247}
1248
1249/**
1250 * Compares the lengths of comment data against the maximum character limits.
1251 *
1252 * @since 4.7.0
1253 *
1254 * @param array $comment_data Array of arguments for inserting a comment.
1255 * @return WP_Error|true WP_Error when a comment field exceeds the limit,
1256 * otherwise true.
1257 */
1258function wp_check_comment_data_max_lengths( $comment_data ) {
1259 $max_lengths = wp_get_comment_fields_max_lengths();
1260
1261 if ( isset( $comment_data['comment_author'] ) && mb_strlen( $comment_data['comment_author'], '8bit' ) > $max_lengths['comment_author'] ) {
1262 return new WP_Error( 'comment_author_column_length', __( '<strong>Error:</strong> Your name is too long.' ), 200 );
1263 }
1264
1265 if ( isset( $comment_data['comment_author_email'] ) && strlen( $comment_data['comment_author_email'] ) > $max_lengths['comment_author_email'] ) {
1266 return new WP_Error( 'comment_author_email_column_length', __( '<strong>Error:</strong> Your email address is too long.' ), 200 );
1267 }
1268
1269 if ( isset( $comment_data['comment_author_url'] ) && strlen( $comment_data['comment_author_url'] ) > $max_lengths['comment_author_url'] ) {
1270 return new WP_Error( 'comment_author_url_column_length', __( '<strong>Error:</strong> Your URL is too long.' ), 200 );
1271 }
1272
1273 if ( isset( $comment_data['comment_content'] ) && mb_strlen( $comment_data['comment_content'], '8bit' ) > $max_lengths['comment_content'] ) {
1274 return new WP_Error( 'comment_content_column_length', __( '<strong>Error:</strong> Your comment is too long.' ), 200 );
1275 }
1276
1277 return true;
1278}
1279
1280/**
1281 * Checks whether comment data passes internal checks or has disallowed content.
1282 *
1283 * @since 6.7.0
1284 *
1285 * @global wpdb $wpdb WordPress database abstraction object.
1286 *
1287 * @param array $comment_data Array of arguments for inserting a comment.
1288 * @return int|string|WP_Error The approval status on success (0|1|'spam'|'trash'),
1289 * WP_Error otherwise.
1290 */
1291function wp_check_comment_data( $comment_data ) {
1292 global $wpdb;
1293
1294 if ( ! empty( $comment_data['user_id'] ) ) {
1295 $user = get_userdata( $comment_data['user_id'] );
1296 $post_author = (int) $wpdb->get_var(
1297 $wpdb->prepare(
1298 "SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
1299 $comment_data['comment_post_ID']
1300 )
1301 );
1302 }
1303
1304 if ( isset( $user ) && ( $comment_data['user_id'] === $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
1305 // The author and the admins get respect.
1306 $approved = 1;
1307 } else {
1308 // Everyone else's comments will be checked.
1309 if ( check_comment(
1310 $comment_data['comment_author'],
1311 $comment_data['comment_author_email'],
1312 $comment_data['comment_author_url'],
1313 $comment_data['comment_content'],
1314 $comment_data['comment_author_IP'],
1315 $comment_data['comment_agent'],
1316 $comment_data['comment_type']
1317 ) ) {
1318 $approved = 1;
1319 } else {
1320 $approved = 0;
1321 }
1322
1323 if ( wp_check_comment_disallowed_list(
1324 $comment_data['comment_author'],
1325 $comment_data['comment_author_email'],
1326 $comment_data['comment_author_url'],
1327 $comment_data['comment_content'],
1328 $comment_data['comment_author_IP'],
1329 $comment_data['comment_agent']
1330 ) ) {
1331 $approved = EMPTY_TRASH_DAYS ? 'trash' : 'spam';
1332 }
1333 }
1334
1335 /**
1336 * Filters a comment's approval status before it is set.
1337 *
1338 * @since 2.1.0
1339 * @since 4.9.0 Returning a WP_Error value from the filter will short-circuit comment insertion
1340 * and allow skipping further processing.
1341 *
1342 * @param int|string|WP_Error $approved The approval status. Accepts 1, 0, 'spam', 'trash',
1343 * or WP_Error.
1344 * @param array $commentdata Comment data.
1345 */
1346 return apply_filters( 'pre_comment_approved', $approved, $comment_data );
1347}
1348
1349/**
1350 * Checks if a comment contains disallowed characters or words.
1351 *
1352 * @since 5.5.0
1353 *
1354 * @param string $author The author of the comment.
1355 * @param string $email The email of the comment.
1356 * @param string $url The url used in the comment.
1357 * @param string $comment The comment content.
1358 * @param string $user_ip The comment author's IP address.
1359 * @param string $user_agent The author's browser user agent.
1360 * @return bool True if the comment contains disallowed content, false otherwise.
1361 */
1362function wp_check_comment_disallowed_list( $author, $email, $url, $comment, $user_ip, $user_agent ) {
1363 /**
1364 * Fires before the comment is tested for disallowed characters or words.
1365 *
1366 * @since 1.5.0
1367 * @deprecated 5.5.0 Use {@see 'wp_check_comment_disallowed_list'} instead.
1368 *
1369 * @param string $author Comment author.
1370 * @param string $email Comment author's email.
1371 * @param string $url Comment author's URL.
1372 * @param string $comment Comment content.
1373 * @param string $user_ip Comment author's IP address.
1374 * @param string $user_agent Comment author's browser user agent.
1375 */
1376 do_action_deprecated(
1377 'wp_blacklist_check',
1378 array( $author, $email, $url, $comment, $user_ip, $user_agent ),
1379 '5.5.0',
1380 'wp_check_comment_disallowed_list',
1381 __( 'Please consider writing more inclusive code.' )
1382 );
1383
1384 /**
1385 * Fires before the comment is tested for disallowed characters or words.
1386 *
1387 * @since 5.5.0
1388 *
1389 * @param string $author Comment author.
1390 * @param string $email Comment author's email.
1391 * @param string $url Comment author's URL.
1392 * @param string $comment Comment content.
1393 * @param string $user_ip Comment author's IP address.
1394 * @param string $user_agent Comment author's browser user agent.
1395 */
1396 do_action( 'wp_check_comment_disallowed_list', $author, $email, $url, $comment, $user_ip, $user_agent );
1397
1398 $mod_keys = trim( get_option( 'disallowed_keys' ) );
1399 if ( '' === $mod_keys ) {
1400 return false; // If moderation keys are empty.
1401 }
1402
1403 // Ensure HTML tags are not being used to bypass the list of disallowed characters and words.
1404 $comment_without_html = wp_strip_all_tags( $comment );
1405
1406 $words = explode( "\n", $mod_keys );
1407
1408 foreach ( (array) $words as $word ) {
1409 $word = trim( $word );
1410
1411 // Skip empty lines.
1412 if ( empty( $word ) ) {
1413 continue; }
1414
1415 // Do some escaping magic so that '#' chars in the spam words don't break things:
1416 $word = preg_quote( $word, '#' );
1417
1418 $pattern = "#$word#iu";
1419 if ( preg_match( $pattern, $author )
1420 || preg_match( $pattern, $email )
1421 || preg_match( $pattern, $url )
1422 || preg_match( $pattern, $comment )
1423 || preg_match( $pattern, $comment_without_html )
1424 || preg_match( $pattern, $user_ip )
1425 || preg_match( $pattern, $user_agent )
1426 ) {
1427 return true;
1428 }
1429 }
1430 return false;
1431}
1432
1433/**
1434 * Retrieves the total comment counts for the whole site or a single post.
1435 *
1436 * The comment stats are cached and then retrieved, if they already exist in the
1437 * cache.
1438 *
1439 * @see get_comment_count() Which handles fetching the live comment counts.
1440 *
1441 * @since 2.5.0
1442 *
1443 * @param int $post_id Optional. Restrict the comment counts to the given post. Default 0, which indicates that
1444 * comment counts for the whole site will be retrieved.
1445 * @return stdClass {
1446 * The number of comments keyed by their status.
1447 *
1448 * @type int $approved The number of approved comments.
1449 * @type int $moderated The number of comments awaiting moderation (a.k.a. pending).
1450 * @type int $spam The number of spam comments.
1451 * @type int $trash The number of trashed comments.
1452 * @type int $post-trashed The number of comments for posts that are in the trash.
1453 * @type int $total_comments The total number of non-trashed comments, including spam.
1454 * @type int $all The total number of pending or approved comments.
1455 * }
1456 */
1457function wp_count_comments( $post_id = 0 ) {
1458 $post_id = (int) $post_id;
1459
1460 /**
1461 * Filters the comments count for a given post or the whole site.
1462 *
1463 * @since 2.7.0
1464 *
1465 * @param array|stdClass $count An empty array or an object containing comment counts.
1466 * @param int $post_id The post ID. Can be 0 to represent the whole site.
1467 */
1468 $filtered = apply_filters( 'wp_count_comments', array(), $post_id );
1469 if ( ! empty( $filtered ) ) {
1470 return $filtered;
1471 }
1472
1473 $count = wp_cache_get( "comments-{$post_id}", 'counts' );
1474 if ( false !== $count ) {
1475 return $count;
1476 }
1477
1478 $stats = get_comment_count( $post_id );
1479 $stats['moderated'] = $stats['awaiting_moderation'];
1480 unset( $stats['awaiting_moderation'] );
1481
1482 $stats_object = (object) $stats;
1483 wp_cache_set( "comments-{$post_id}", $stats_object, 'counts' );
1484
1485 return $stats_object;
1486}
1487
1488/**
1489 * Trashes or deletes a comment.
1490 *
1491 * The comment is moved to Trash instead of permanently deleted unless Trash is
1492 * disabled, item is already in the Trash, or $force_delete is true.
1493 *
1494 * The post comment count will be updated if the comment was approved and has a
1495 * post ID available.
1496 *
1497 * @since 2.0.0
1498 *
1499 * @global wpdb $wpdb WordPress database abstraction object.
1500 *
1501 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1502 * @param bool $force_delete Whether to bypass Trash and force deletion. Default false.
1503 * @return bool True on success, false on failure.
1504 */
1505function wp_delete_comment( $comment_id, $force_delete = false ) {
1506 global $wpdb;
1507
1508 $comment = get_comment( $comment_id );
1509 if ( ! $comment ) {
1510 return false;
1511 }
1512
1513 if ( ! $force_delete && EMPTY_TRASH_DAYS && ! in_array( wp_get_comment_status( $comment ), array( 'trash', 'spam' ), true ) ) {
1514 return wp_trash_comment( $comment_id );
1515 }
1516
1517 /**
1518 * Fires immediately before a comment is deleted from the database.
1519 *
1520 * @since 1.2.0
1521 * @since 4.9.0 Added the `$comment` parameter.
1522 *
1523 * @param string $comment_id The comment ID as a numeric string.
1524 * @param WP_Comment $comment The comment to be deleted.
1525 */
1526 do_action( 'delete_comment', $comment->comment_ID, $comment );
1527
1528 // Move children up a level.
1529 $children = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_parent = %d", $comment->comment_ID ) );
1530 if ( ! empty( $children ) ) {
1531 $wpdb->update( $wpdb->comments, array( 'comment_parent' => $comment->comment_parent ), array( 'comment_parent' => $comment->comment_ID ) );
1532 clean_comment_cache( $children );
1533 }
1534
1535 // Delete metadata.
1536 $meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) );
1537 foreach ( $meta_ids as $mid ) {
1538 delete_metadata_by_mid( 'comment', $mid );
1539 }
1540
1541 if ( ! $wpdb->delete( $wpdb->comments, array( 'comment_ID' => $comment->comment_ID ) ) ) {
1542 return false;
1543 }
1544
1545 /**
1546 * Fires immediately after a comment is deleted from the database.
1547 *
1548 * @since 2.9.0
1549 * @since 4.9.0 Added the `$comment` parameter.
1550 *
1551 * @param string $comment_id The comment ID as a numeric string.
1552 * @param WP_Comment $comment The deleted comment.
1553 */
1554 do_action( 'deleted_comment', $comment->comment_ID, $comment );
1555
1556 $post_id = $comment->comment_post_ID;
1557 if ( $post_id && '1' === $comment->comment_approved ) {
1558 wp_update_comment_count( $post_id );
1559 }
1560
1561 clean_comment_cache( $comment->comment_ID );
1562
1563 /** This action is documented in wp-includes/comment.php */
1564 do_action( 'wp_set_comment_status', $comment->comment_ID, 'delete' );
1565
1566 wp_transition_comment_status( 'delete', $comment->comment_approved, $comment );
1567
1568 return true;
1569}
1570
1571/**
1572 * Moves a comment to the Trash
1573 *
1574 * If Trash is disabled, comment is permanently deleted.
1575 *
1576 * @since 2.9.0
1577 * @since 6.9.0 Any child notes are deleted when deleting a note.
1578 *
1579 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1580 * @return bool True on success, false on failure.
1581 */
1582function wp_trash_comment( $comment_id ) {
1583 if ( ! EMPTY_TRASH_DAYS ) {
1584 $comment = get_comment( $comment_id );
1585 $success = wp_delete_comment( $comment_id, true );
1586
1587 if ( ! $success ) {
1588 return false;
1589 }
1590
1591 // Also delete children of top level 'note' type comments.
1592 if ( $comment && 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) {
1593 $children = $comment->get_children(
1594 array(
1595 'fields' => 'ids',
1596 'status' => 'all',
1597 'type' => 'note',
1598 )
1599 );
1600
1601 foreach ( $children as $child_id ) {
1602 if ( ! wp_delete_comment( $child_id, true ) ) {
1603 $success = false;
1604 }
1605 }
1606 }
1607
1608 return $success;
1609 }
1610
1611 $comment = get_comment( $comment_id );
1612 if ( ! $comment ) {
1613 return false;
1614 }
1615
1616 /**
1617 * Fires immediately before a comment is sent to the Trash.
1618 *
1619 * @since 2.9.0
1620 * @since 4.9.0 Added the `$comment` parameter.
1621 *
1622 * @param string $comment_id The comment ID as a numeric string.
1623 * @param WP_Comment $comment The comment to be trashed.
1624 */
1625 do_action( 'trash_comment', $comment->comment_ID, $comment );
1626
1627 if ( wp_set_comment_status( $comment, 'trash' ) ) {
1628 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1629 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1630 add_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', $comment->comment_approved );
1631 add_comment_meta( $comment->comment_ID, '_wp_trash_meta_time', time() );
1632
1633 /**
1634 * Fires immediately after a comment is sent to Trash.
1635 *
1636 * @since 2.9.0
1637 * @since 4.9.0 Added the `$comment` parameter.
1638 *
1639 * @param string $comment_id The comment ID as a numeric string.
1640 * @param WP_Comment $comment The trashed comment.
1641 */
1642 do_action( 'trashed_comment', $comment->comment_ID, $comment );
1643
1644 // For top level 'note' type comments, also trash children.
1645 if ( 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) {
1646 $children = $comment->get_children(
1647 array(
1648 'fields' => 'ids',
1649 'status' => 'all',
1650 'type' => 'note',
1651 )
1652 );
1653
1654 $success = true;
1655 foreach ( $children as $child_id ) {
1656 if ( ! wp_trash_comment( $child_id ) ) {
1657 $success = false;
1658 }
1659 }
1660 return $success;
1661 }
1662
1663 return true;
1664 }
1665
1666 return false;
1667}
1668
1669/**
1670 * Removes a comment from the Trash
1671 *
1672 * @since 2.9.0
1673 *
1674 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1675 * @return bool True on success, false on failure.
1676 */
1677function wp_untrash_comment( $comment_id ) {
1678 $comment = get_comment( $comment_id );
1679 if ( ! $comment ) {
1680 return false;
1681 }
1682
1683 /**
1684 * Fires immediately before a comment is restored from the Trash.
1685 *
1686 * @since 2.9.0
1687 * @since 4.9.0 Added the `$comment` parameter.
1688 *
1689 * @param string $comment_id The comment ID as a numeric string.
1690 * @param WP_Comment $comment The comment to be untrashed.
1691 */
1692 do_action( 'untrash_comment', $comment->comment_ID, $comment );
1693
1694 $status = (string) get_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', true );
1695 if ( empty( $status ) ) {
1696 $status = '0';
1697 }
1698
1699 if ( wp_set_comment_status( $comment, $status ) ) {
1700 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1701 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1702
1703 /**
1704 * Fires immediately after a comment is restored from the Trash.
1705 *
1706 * @since 2.9.0
1707 * @since 4.9.0 Added the `$comment` parameter.
1708 *
1709 * @param string $comment_id The comment ID as a numeric string.
1710 * @param WP_Comment $comment The untrashed comment.
1711 */
1712 do_action( 'untrashed_comment', $comment->comment_ID, $comment );
1713
1714 return true;
1715 }
1716
1717 return false;
1718}
1719
1720/**
1721 * Marks a comment as Spam.
1722 *
1723 * @since 2.9.0
1724 *
1725 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1726 * @return bool True on success, false on failure.
1727 */
1728function wp_spam_comment( $comment_id ) {
1729 $comment = get_comment( $comment_id );
1730 if ( ! $comment ) {
1731 return false;
1732 }
1733
1734 /**
1735 * Fires immediately before a comment is marked as Spam.
1736 *
1737 * @since 2.9.0
1738 * @since 4.9.0 Added the `$comment` parameter.
1739 *
1740 * @param int $comment_id The comment ID.
1741 * @param WP_Comment $comment The comment to be marked as spam.
1742 */
1743 do_action( 'spam_comment', $comment->comment_ID, $comment );
1744
1745 if ( wp_set_comment_status( $comment, 'spam' ) ) {
1746 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1747 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1748 add_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', $comment->comment_approved );
1749 add_comment_meta( $comment->comment_ID, '_wp_trash_meta_time', time() );
1750
1751 /**
1752 * Fires immediately after a comment is marked as Spam.
1753 *
1754 * @since 2.9.0
1755 * @since 4.9.0 Added the `$comment` parameter.
1756 *
1757 * @param int $comment_id The comment ID.
1758 * @param WP_Comment $comment The comment marked as spam.
1759 */
1760 do_action( 'spammed_comment', $comment->comment_ID, $comment );
1761
1762 return true;
1763 }
1764
1765 return false;
1766}
1767
1768/**
1769 * Removes a comment from the Spam.
1770 *
1771 * @since 2.9.0
1772 *
1773 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1774 * @return bool True on success, false on failure.
1775 */
1776function wp_unspam_comment( $comment_id ) {
1777 $comment = get_comment( $comment_id );
1778 if ( ! $comment ) {
1779 return false;
1780 }
1781
1782 /**
1783 * Fires immediately before a comment is unmarked as Spam.
1784 *
1785 * @since 2.9.0
1786 * @since 4.9.0 Added the `$comment` parameter.
1787 *
1788 * @param string $comment_id The comment ID as a numeric string.
1789 * @param WP_Comment $comment The comment to be unmarked as spam.
1790 */
1791 do_action( 'unspam_comment', $comment->comment_ID, $comment );
1792
1793 $status = (string) get_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', true );
1794 if ( empty( $status ) ) {
1795 $status = '0';
1796 }
1797
1798 if ( wp_set_comment_status( $comment, $status ) ) {
1799 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_status' );
1800 delete_comment_meta( $comment->comment_ID, '_wp_trash_meta_time' );
1801
1802 /**
1803 * Fires immediately after a comment is unmarked as Spam.
1804 *
1805 * @since 2.9.0
1806 * @since 4.9.0 Added the `$comment` parameter.
1807 *
1808 * @param string $comment_id The comment ID as a numeric string.
1809 * @param WP_Comment $comment The comment unmarked as spam.
1810 */
1811 do_action( 'unspammed_comment', $comment->comment_ID, $comment );
1812
1813 return true;
1814 }
1815
1816 return false;
1817}
1818
1819/**
1820 * Retrieves the status of a comment by comment ID.
1821 *
1822 * @since 1.0.0
1823 *
1824 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object
1825 * @return string|false Status might be 'trash', 'approved', 'unapproved', 'spam'. False on failure.
1826 */
1827function wp_get_comment_status( $comment_id ) {
1828 $comment = get_comment( $comment_id );
1829 if ( ! $comment ) {
1830 return false;
1831 }
1832
1833 $approved = $comment->comment_approved;
1834
1835 if ( null === $approved ) {
1836 return false;
1837 } elseif ( '1' === $approved ) {
1838 return 'approved';
1839 } elseif ( '0' === $approved ) {
1840 return 'unapproved';
1841 } elseif ( 'spam' === $approved ) {
1842 return 'spam';
1843 } elseif ( 'trash' === $approved ) {
1844 return 'trash';
1845 } else {
1846 return false;
1847 }
1848}
1849
1850/**
1851 * Calls hooks for when a comment status transition occurs.
1852 *
1853 * Calls hooks for comment status transitions. If the new comment status is not the same
1854 * as the previous comment status, then two hooks will be ran, the first is
1855 * {@see 'transition_comment_status'} with new status, old status, and comment data.
1856 * The next action called is {@see 'comment_$old_status_to_$new_status'}. It has
1857 * the comment data.
1858 *
1859 * The final action will run whether or not the comment statuses are the same.
1860 * The action is named {@see 'comment_$new_status_$comment->comment_type'}.
1861 *
1862 * @since 2.7.0
1863 *
1864 * @param string $new_status New comment status.
1865 * @param string $old_status Previous comment status.
1866 * @param WP_Comment $comment Comment object.
1867 */
1868function wp_transition_comment_status( $new_status, $old_status, $comment ) {
1869 /*
1870 * Translate raw statuses to human-readable formats for the hooks.
1871 * This is not a complete list of comment status, it's only the ones
1872 * that need to be renamed.
1873 */
1874 $comment_statuses = array(
1875 0 => 'unapproved',
1876 'hold' => 'unapproved', // wp_set_comment_status() uses "hold".
1877 1 => 'approved',
1878 'approve' => 'approved', // wp_set_comment_status() uses "approve".
1879 );
1880 if ( isset( $comment_statuses[ $new_status ] ) ) {
1881 $new_status = $comment_statuses[ $new_status ];
1882 }
1883 if ( isset( $comment_statuses[ $old_status ] ) ) {
1884 $old_status = $comment_statuses[ $old_status ];
1885 }
1886
1887 // Call the hooks.
1888 if ( $new_status !== $old_status ) {
1889 /**
1890 * Fires when the comment status is in transition.
1891 *
1892 * @since 2.7.0
1893 *
1894 * @param string $new_status The new comment status.
1895 * @param string $old_status The old comment status.
1896 * @param WP_Comment $comment Comment object.
1897 */
1898 do_action( 'transition_comment_status', $new_status, $old_status, $comment );
1899
1900 /**
1901 * Fires when the comment status is in transition from one specific status to another.
1902 *
1903 * The dynamic portions of the hook name, `$old_status`, and `$new_status`,
1904 * refer to the old and new comment statuses, respectively.
1905 *
1906 * Possible hook names include:
1907 *
1908 * - `comment_unapproved_to_approved`
1909 * - `comment_spam_to_approved`
1910 * - `comment_approved_to_unapproved`
1911 * - `comment_spam_to_unapproved`
1912 * - `comment_unapproved_to_spam`
1913 * - `comment_approved_to_spam`
1914 *
1915 * @since 2.7.0
1916 *
1917 * @param WP_Comment $comment Comment object.
1918 */
1919 do_action( "comment_{$old_status}_to_{$new_status}", $comment );
1920 }
1921 /**
1922 * Fires when the status of a specific comment type is in transition.
1923 *
1924 * The dynamic portions of the hook name, `$new_status`, and `$comment->comment_type`,
1925 * refer to the new comment status, and the type of comment, respectively.
1926 *
1927 * Typical comment types include 'comment', 'pingback', or 'trackback'.
1928 *
1929 * Possible hook names include:
1930 *
1931 * - `comment_approved_comment`
1932 * - `comment_approved_pingback`
1933 * - `comment_approved_trackback`
1934 * - `comment_unapproved_comment`
1935 * - `comment_unapproved_pingback`
1936 * - `comment_unapproved_trackback`
1937 * - `comment_spam_comment`
1938 * - `comment_spam_pingback`
1939 * - `comment_spam_trackback`
1940 *
1941 * @since 2.7.0
1942 *
1943 * @param string $comment_id The comment ID as a numeric string.
1944 * @param WP_Comment $comment Comment object.
1945 */
1946 do_action( "comment_{$new_status}_{$comment->comment_type}", $comment->comment_ID, $comment );
1947}
1948
1949/**
1950 * Clears the lastcommentmodified cached value when a comment status is changed.
1951 *
1952 * Deletes the lastcommentmodified cache key when a comment enters or leaves
1953 * 'approved' status.
1954 *
1955 * @since 4.7.0
1956 * @access private
1957 *
1958 * @param string $new_status The new comment status.
1959 * @param string $old_status The old comment status.
1960 */
1961function _clear_modified_cache_on_transition_comment_status( $new_status, $old_status ) {
1962 if ( 'approved' === $new_status || 'approved' === $old_status ) {
1963 $data = array();
1964 foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
1965 $data[] = "lastcommentmodified:$timezone";
1966 }
1967 wp_cache_delete_multiple( $data, 'timeinfo' );
1968 }
1969}
1970
1971/**
1972 * Gets current commenter's name, email, and URL.
1973 *
1974 * Expects cookies content to already be sanitized. User of this function might
1975 * wish to recheck the returned array for validity.
1976 *
1977 * @see sanitize_comment_cookies() Use to sanitize cookies
1978 *
1979 * @since 2.0.4
1980 *
1981 * @return array {
1982 * An array of current commenter variables.
1983 *
1984 * @type string $comment_author The name of the current commenter, or an empty string.
1985 * @type string $comment_author_email The email address of the current commenter, or an empty string.
1986 * @type string $comment_author_url The URL address of the current commenter, or an empty string.
1987 * }
1988 */
1989function wp_get_current_commenter() {
1990 // Cookies should already be sanitized.
1991
1992 $comment_author = '';
1993 if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
1994 $comment_author = $_COOKIE[ 'comment_author_' . COOKIEHASH ];
1995 }
1996
1997 $comment_author_email = '';
1998 if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
1999 $comment_author_email = $_COOKIE[ 'comment_author_email_' . COOKIEHASH ];
2000 }
2001
2002 $comment_author_url = '';
2003 if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
2004 $comment_author_url = $_COOKIE[ 'comment_author_url_' . COOKIEHASH ];
2005 }
2006
2007 /**
2008 * Filters the current commenter's name, email, and URL.
2009 *
2010 * @since 3.1.0
2011 *
2012 * @param array $comment_author_data {
2013 * An array of current commenter variables.
2014 *
2015 * @type string $comment_author The name of the current commenter, or an empty string.
2016 * @type string $comment_author_email The email address of the current commenter, or an empty string.
2017 * @type string $comment_author_url The URL address of the current commenter, or an empty string.
2018 * }
2019 */
2020 return apply_filters( 'wp_get_current_commenter', compact( 'comment_author', 'comment_author_email', 'comment_author_url' ) );
2021}
2022
2023/**
2024 * Gets unapproved comment author's email.
2025 *
2026 * Used to allow the commenter to see their pending comment.
2027 *
2028 * @since 5.1.0
2029 * @since 5.7.0 The window within which the author email for an unapproved comment
2030 * can be retrieved was extended to 10 minutes.
2031 *
2032 * @return string The unapproved comment author's email (when supplied).
2033 */
2034function wp_get_unapproved_comment_author_email() {
2035 $commenter_email = '';
2036
2037 if ( ! empty( $_GET['unapproved'] ) && ! empty( $_GET['moderation-hash'] ) ) {
2038 $comment_id = (int) $_GET['unapproved'];
2039 $comment = get_comment( $comment_id );
2040
2041 if ( $comment && hash_equals( $_GET['moderation-hash'], wp_hash( $comment->comment_date_gmt ) ) ) {
2042 // The comment will only be viewable by the comment author for 10 minutes.
2043 $comment_preview_expires = strtotime( $comment->comment_date_gmt . '+10 minutes' );
2044
2045 if ( time() < $comment_preview_expires ) {
2046 $commenter_email = $comment->comment_author_email;
2047 }
2048 }
2049 }
2050
2051 if ( ! $commenter_email ) {
2052 $commenter = wp_get_current_commenter();
2053 $commenter_email = $commenter['comment_author_email'];
2054 }
2055
2056 return $commenter_email;
2057}
2058
2059/**
2060 * Inserts a comment into the database.
2061 *
2062 * @since 2.0.0
2063 * @since 4.4.0 Introduced the `$comment_meta` argument.
2064 * @since 5.5.0 Default value for `$comment_type` argument changed to `comment`.
2065 *
2066 * @global wpdb $wpdb WordPress database abstraction object.
2067 *
2068 * @param array $commentdata {
2069 * Array of arguments for inserting a new comment.
2070 *
2071 * @type string $comment_agent The HTTP user agent of the `$comment_author` when
2072 * the comment was submitted. Default empty.
2073 * @type int|string $comment_approved Whether the comment has been approved. Default 1.
2074 * @type string $comment_author The name of the author of the comment. Default empty.
2075 * @type string $comment_author_email The email address of the `$comment_author`. Default empty.
2076 * @type string $comment_author_IP The IP address of the `$comment_author`. Default empty.
2077 * @type string $comment_author_url The URL address of the `$comment_author`. Default empty.
2078 * @type string $comment_content The content of the comment. Default empty.
2079 * @type string $comment_date The date the comment was submitted. To set the date
2080 * manually, `$comment_date_gmt` must also be specified.
2081 * Default is the current time.
2082 * @type string $comment_date_gmt The date the comment was submitted in the GMT timezone.
2083 * Default is `$comment_date` in the site's GMT timezone.
2084 * @type int $comment_karma The karma of the comment. Default 0.
2085 * @type int $comment_parent ID of this comment's parent, if any. Default 0.
2086 * @type int $comment_post_ID ID of the post that relates to the comment, if any.
2087 * Default 0.
2088 * @type string $comment_type Comment type. Default 'comment'.
2089 * @type array $comment_meta Optional. Array of key/value pairs to be stored in commentmeta for the
2090 * new comment.
2091 * @type int $user_id ID of the user who submitted the comment. Default 0.
2092 * }
2093 * @return int|false The new comment's ID on success, false on failure.
2094 */
2095function wp_insert_comment( $commentdata ) {
2096 global $wpdb;
2097
2098 $data = wp_unslash( $commentdata );
2099
2100 $comment_author = ! isset( $data['comment_author'] ) ? '' : $data['comment_author'];
2101 $comment_author_email = ! isset( $data['comment_author_email'] ) ? '' : $data['comment_author_email'];
2102 $comment_author_url = ! isset( $data['comment_author_url'] ) ? '' : $data['comment_author_url'];
2103 $comment_author_ip = ! isset( $data['comment_author_IP'] ) ? '' : $data['comment_author_IP'];
2104
2105 $comment_date = ! isset( $data['comment_date'] ) ? current_time( 'mysql' ) : $data['comment_date'];
2106 $comment_date_gmt = ! isset( $data['comment_date_gmt'] ) ? get_gmt_from_date( $comment_date ) : $data['comment_date_gmt'];
2107
2108 $comment_post_id = ! isset( $data['comment_post_ID'] ) ? 0 : $data['comment_post_ID'];
2109 $comment_content = ! isset( $data['comment_content'] ) ? '' : $data['comment_content'];
2110 $comment_karma = ! isset( $data['comment_karma'] ) ? 0 : $data['comment_karma'];
2111 $comment_approved = ! isset( $data['comment_approved'] ) ? 1 : $data['comment_approved'];
2112 $comment_agent = ! isset( $data['comment_agent'] ) ? '' : $data['comment_agent'];
2113 $comment_type = empty( $data['comment_type'] ) ? 'comment' : $data['comment_type'];
2114 $comment_parent = ! isset( $data['comment_parent'] ) ? 0 : $data['comment_parent'];
2115
2116 $user_id = ! isset( $data['user_id'] ) ? 0 : $data['user_id'];
2117
2118 $compacted = array(
2119 'comment_post_ID' => $comment_post_id,
2120 'comment_author_IP' => $comment_author_ip,
2121 );
2122
2123 $compacted += compact(
2124 'comment_author',
2125 'comment_author_email',
2126 'comment_author_url',
2127 'comment_date',
2128 'comment_date_gmt',
2129 'comment_content',
2130 'comment_karma',
2131 'comment_approved',
2132 'comment_agent',
2133 'comment_type',
2134 'comment_parent',
2135 'user_id'
2136 );
2137
2138 if ( ! $wpdb->insert( $wpdb->comments, $compacted ) ) {
2139 return false;
2140 }
2141
2142 $id = (int) $wpdb->insert_id;
2143
2144 if ( 1 === (int) $comment_approved ) {
2145 wp_update_comment_count( $comment_post_id );
2146
2147 $data = array();
2148 foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
2149 $data[] = "lastcommentmodified:$timezone";
2150 }
2151 wp_cache_delete_multiple( $data, 'timeinfo' );
2152 }
2153
2154 clean_comment_cache( $id );
2155
2156 $comment = get_comment( $id );
2157
2158 // If metadata is provided, store it.
2159 if ( isset( $commentdata['comment_meta'] ) && is_array( $commentdata['comment_meta'] ) ) {
2160 foreach ( $commentdata['comment_meta'] as $meta_key => $meta_value ) {
2161 add_comment_meta( $comment->comment_ID, $meta_key, $meta_value, true );
2162 }
2163 }
2164
2165 /**
2166 * Fires immediately after a comment is inserted into the database.
2167 *
2168 * @since 2.8.0
2169 *
2170 * @param int $id The comment ID.
2171 * @param WP_Comment $comment Comment object.
2172 */
2173 do_action( 'wp_insert_comment', $id, $comment );
2174
2175 return $id;
2176}
2177
2178/**
2179 * Filters and sanitizes comment data.
2180 *
2181 * Sets the comment data 'filtered' field to true when finished. This can be
2182 * checked as to whether the comment should be filtered and to keep from
2183 * filtering the same comment more than once.
2184 *
2185 * @since 2.0.0
2186 *
2187 * @param array $commentdata Contains information on the comment.
2188 * @return array Parsed comment information.
2189 */
2190function wp_filter_comment( $commentdata ) {
2191 if ( isset( $commentdata['user_ID'] ) ) {
2192 /**
2193 * Filters the comment author's user ID before it is set.
2194 *
2195 * The first time this filter is evaluated, `user_ID` is checked
2196 * (for back-compat), followed by the standard `user_id` value.
2197 *
2198 * @since 1.5.0
2199 *
2200 * @param int $user_id The comment author's user ID.
2201 */
2202 $commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_ID'] );
2203 } elseif ( isset( $commentdata['user_id'] ) ) {
2204 /** This filter is documented in wp-includes/comment.php */
2205 $commentdata['user_id'] = apply_filters( 'pre_user_id', $commentdata['user_id'] );
2206 }
2207
2208 /**
2209 * Filters the comment author's browser user agent before it is set.
2210 *
2211 * @since 1.5.0
2212 *
2213 * @param string $comment_agent The comment author's browser user agent.
2214 */
2215 $commentdata['comment_agent'] = apply_filters( 'pre_comment_user_agent', ( isset( $commentdata['comment_agent'] ) ? $commentdata['comment_agent'] : '' ) );
2216 /** This filter is documented in wp-includes/comment.php */
2217 $commentdata['comment_author'] = apply_filters( 'pre_comment_author_name', $commentdata['comment_author'] );
2218 /**
2219 * Filters the comment content before it is set.
2220 *
2221 * @since 1.5.0
2222 *
2223 * @param string $comment_content The comment content.
2224 */
2225 $commentdata['comment_content'] = apply_filters( 'pre_comment_content', $commentdata['comment_content'] );
2226 /**
2227 * Filters the comment author's IP address before it is set.
2228 *
2229 * @since 1.5.0
2230 *
2231 * @param string $comment_author_ip The comment author's IP address.
2232 */
2233 $commentdata['comment_author_IP'] = apply_filters( 'pre_comment_user_ip', $commentdata['comment_author_IP'] );
2234 /** This filter is documented in wp-includes/comment.php */
2235 $commentdata['comment_author_url'] = apply_filters( 'pre_comment_author_url', $commentdata['comment_author_url'] );
2236 /** This filter is documented in wp-includes/comment.php */
2237 $commentdata['comment_author_email'] = apply_filters( 'pre_comment_author_email', $commentdata['comment_author_email'] );
2238
2239 $commentdata['filtered'] = true;
2240
2241 return $commentdata;
2242}
2243
2244/**
2245 * Determines whether a comment should be blocked because of comment flood.
2246 *
2247 * @since 2.1.0
2248 *
2249 * @param bool $block Whether plugin has already blocked comment.
2250 * @param int $time_lastcomment Timestamp for last comment.
2251 * @param int $time_newcomment Timestamp for new comment.
2252 * @return bool Whether comment should be blocked.
2253 */
2254function wp_throttle_comment_flood( $block, $time_lastcomment, $time_newcomment ) {
2255 if ( $block ) { // A plugin has already blocked... we'll let that decision stand.
2256 return $block;
2257 }
2258 if ( ( $time_newcomment - $time_lastcomment ) < 15 ) {
2259 return true;
2260 }
2261 return false;
2262}
2263
2264/**
2265 * Adds a new comment to the database.
2266 *
2267 * Filters new comment to ensure that the fields are sanitized and valid before
2268 * inserting comment into database. Calls {@see 'comment_post'} action with comment ID
2269 * and whether comment is approved by WordPress. Also has {@see 'preprocess_comment'}
2270 * filter for processing the comment data before the function handles it.
2271 *
2272 * We use `REMOTE_ADDR` here directly. If you are behind a proxy, you should ensure
2273 * that it is properly set, such as in wp-config.php, for your environment.
2274 *
2275 * See {@link https://core.trac.wordpress.org/ticket/9235}
2276 *
2277 * @since 1.5.0
2278 * @since 4.3.0 Introduced the `comment_agent` and `comment_author_IP` arguments.
2279 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function
2280 * to return a WP_Error object instead of dying.
2281 * @since 5.5.0 The `$avoid_die` parameter was renamed to `$wp_error`.
2282 * @since 5.5.0 Introduced the `comment_type` argument.
2283 *
2284 * @see wp_insert_comment()
2285 * @global wpdb $wpdb WordPress database abstraction object.
2286 *
2287 * @param array $commentdata {
2288 * Comment data.
2289 *
2290 * @type string $comment_author The name of the comment author.
2291 * @type string $comment_author_email The comment author email address.
2292 * @type string $comment_author_url The comment author URL.
2293 * @type string $comment_content The content of the comment.
2294 * @type string $comment_date The date the comment was submitted. Default is the current time.
2295 * @type string $comment_date_gmt The date the comment was submitted in the GMT timezone.
2296 * Default is `$comment_date` in the GMT timezone.
2297 * @type string $comment_type Comment type. Default 'comment'.
2298 * @type int $comment_parent The ID of this comment's parent, if any. Default 0.
2299 * @type int $comment_post_ID The ID of the post that relates to the comment.
2300 * @type int $user_id The ID of the user who submitted the comment. Default 0.
2301 * @type int $user_ID Kept for backward-compatibility. Use `$user_id` instead.
2302 * @type string $comment_agent Comment author user agent. Default is the value of 'HTTP_USER_AGENT'
2303 * in the `$_SERVER` superglobal sent in the original request.
2304 * @type string $comment_author_IP Comment author IP address in IPv4 format. Default is the value of
2305 * 'REMOTE_ADDR' in the `$_SERVER` superglobal sent in the original request.
2306 * }
2307 * @param bool $wp_error Should errors be returned as WP_Error objects instead of
2308 * executing wp_die()? Default false.
2309 * @return int|false|WP_Error The ID of the comment on success, false or WP_Error on failure.
2310 */
2311function wp_new_comment( $commentdata, $wp_error = false ) {
2312 global $wpdb;
2313
2314 /*
2315 * Normalize `user_ID` to `user_id`, but pass the old key
2316 * to the `preprocess_comment` filter for backward compatibility.
2317 */
2318 if ( isset( $commentdata['user_ID'] ) ) {
2319 $commentdata['user_ID'] = (int) $commentdata['user_ID'];
2320 $commentdata['user_id'] = $commentdata['user_ID'];
2321 } elseif ( isset( $commentdata['user_id'] ) ) {
2322 $commentdata['user_id'] = (int) $commentdata['user_id'];
2323 $commentdata['user_ID'] = $commentdata['user_id'];
2324 }
2325
2326 $prefiltered_user_id = ( isset( $commentdata['user_id'] ) ) ? (int) $commentdata['user_id'] : 0;
2327
2328 if ( ! isset( $commentdata['comment_author_IP'] ) ) {
2329 $commentdata['comment_author_IP'] = $_SERVER['REMOTE_ADDR'];
2330 }
2331
2332 if ( ! isset( $commentdata['comment_agent'] ) ) {
2333 $commentdata['comment_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
2334 }
2335
2336 /**
2337 * Filters a comment's data before it is sanitized and inserted into the database.
2338 *
2339 * @since 1.5.0
2340 * @since 5.6.0 Comment data includes the `comment_agent` and `comment_author_IP` values.
2341 *
2342 * @param array $commentdata Comment data.
2343 */
2344 $commentdata = apply_filters( 'preprocess_comment', $commentdata );
2345
2346 $commentdata['comment_post_ID'] = (int) $commentdata['comment_post_ID'];
2347
2348 // Normalize `user_ID` to `user_id` again, after the filter.
2349 if ( isset( $commentdata['user_ID'] ) && $prefiltered_user_id !== (int) $commentdata['user_ID'] ) {
2350 $commentdata['user_ID'] = (int) $commentdata['user_ID'];
2351 $commentdata['user_id'] = $commentdata['user_ID'];
2352 } elseif ( isset( $commentdata['user_id'] ) ) {
2353 $commentdata['user_id'] = (int) $commentdata['user_id'];
2354 $commentdata['user_ID'] = $commentdata['user_id'];
2355 }
2356
2357 $commentdata['comment_parent'] = isset( $commentdata['comment_parent'] ) ? absint( $commentdata['comment_parent'] ) : 0;
2358
2359 $parent_status = ( $commentdata['comment_parent'] > 0 ) ? wp_get_comment_status( $commentdata['comment_parent'] ) : '';
2360
2361 $commentdata['comment_parent'] = ( 'approved' === $parent_status || 'unapproved' === $parent_status ) ? $commentdata['comment_parent'] : 0;
2362
2363 $commentdata['comment_author_IP'] = preg_replace( '/[^0-9a-fA-F:., ]/', '', $commentdata['comment_author_IP'] );
2364
2365 $commentdata['comment_agent'] = substr( $commentdata['comment_agent'], 0, 254 );
2366
2367 if ( empty( $commentdata['comment_date'] ) ) {
2368 $commentdata['comment_date'] = current_time( 'mysql' );
2369 }
2370
2371 if ( empty( $commentdata['comment_date_gmt'] ) ) {
2372 $commentdata['comment_date_gmt'] = current_time( 'mysql', true );
2373 }
2374
2375 if ( empty( $commentdata['comment_type'] ) ) {
2376 $commentdata['comment_type'] = 'comment';
2377 }
2378
2379 $commentdata['comment_approved'] = wp_allow_comment( $commentdata, $wp_error );
2380
2381 if ( is_wp_error( $commentdata['comment_approved'] ) ) {
2382 return $commentdata['comment_approved'];
2383 }
2384
2385 $commentdata = wp_filter_comment( $commentdata );
2386
2387 if ( ! in_array( $commentdata['comment_approved'], array( 'trash', 'spam' ), true ) ) {
2388 // Validate the comment again after filters are applied to comment data.
2389 $commentdata['comment_approved'] = wp_check_comment_data( $commentdata );
2390 }
2391
2392 if ( is_wp_error( $commentdata['comment_approved'] ) ) {
2393 return $commentdata['comment_approved'];
2394 }
2395
2396 $comment_id = wp_insert_comment( $commentdata );
2397
2398 if ( ! $comment_id ) {
2399 $fields = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content' );
2400
2401 foreach ( $fields as $field ) {
2402 if ( isset( $commentdata[ $field ] ) ) {
2403 $commentdata[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->comments, $field, $commentdata[ $field ] );
2404 }
2405 }
2406
2407 $commentdata = wp_filter_comment( $commentdata );
2408
2409 $commentdata['comment_approved'] = wp_allow_comment( $commentdata, $wp_error );
2410 if ( is_wp_error( $commentdata['comment_approved'] ) ) {
2411 return $commentdata['comment_approved'];
2412 }
2413
2414 $comment_id = wp_insert_comment( $commentdata );
2415 if ( ! $comment_id ) {
2416 return false;
2417 }
2418 }
2419
2420 /**
2421 * Fires immediately after a comment is inserted into the database.
2422 *
2423 * @since 1.2.0
2424 * @since 4.5.0 The `$commentdata` parameter was added.
2425 *
2426 * @param int $comment_id The comment ID.
2427 * @param int|string $comment_approved 1 if the comment is approved, 0 if not, 'spam' if spam.
2428 * @param array $commentdata Comment data.
2429 */
2430 do_action( 'comment_post', $comment_id, $commentdata['comment_approved'], $commentdata );
2431
2432 return $comment_id;
2433}
2434
2435/**
2436 * Sends a comment moderation notification to the comment moderator.
2437 *
2438 * @since 4.4.0
2439 *
2440 * @param int $comment_id ID of the comment.
2441 * @return bool True on success, false on failure.
2442 */
2443function wp_new_comment_notify_moderator( $comment_id ) {
2444 $comment = get_comment( $comment_id );
2445
2446 // Only send notifications for pending comments.
2447 $maybe_notify = ( '0' === $comment->comment_approved );
2448
2449 /** This filter is documented in wp-includes/pluggable.php */
2450 $maybe_notify = apply_filters( 'notify_moderator', $maybe_notify, $comment_id );
2451
2452 if ( ! $maybe_notify ) {
2453 return false;
2454 }
2455
2456 return wp_notify_moderator( $comment_id );
2457}
2458
2459/**
2460 * Sends a notification of a new comment to the post author.
2461 *
2462 * @since 4.4.0
2463 *
2464 * Uses the {@see 'notify_post_author'} filter to determine whether the post author
2465 * should be notified when a new comment is added, overriding site setting.
2466 *
2467 * @param int $comment_id Comment ID.
2468 * @return bool True on success, false on failure.
2469 */
2470function wp_new_comment_notify_postauthor( $comment_id ) {
2471 $comment = get_comment( $comment_id );
2472 $is_note = ( $comment && 'note' === $comment->comment_type );
2473
2474 $maybe_notify = $is_note ? get_option( 'wp_notes_notify', 1 ) : get_option( 'comments_notify' );
2475
2476 /**
2477 * Filters whether to send the post author new comment notification emails,
2478 * overriding the site setting.
2479 *
2480 * @since 4.4.0
2481 *
2482 * @param bool $maybe_notify Whether to notify the post author about the new comment.
2483 * @param int $comment_id The ID of the comment for the notification.
2484 */
2485 $maybe_notify = apply_filters( 'notify_post_author', $maybe_notify, $comment_id );
2486
2487 /*
2488 * wp_notify_postauthor() checks if notifying the author of their own comment.
2489 * By default, it won't, but filters can override this.
2490 */
2491 if ( ! $maybe_notify ) {
2492 return false;
2493 }
2494
2495 // Send notifications for approved comments and all notes.
2496 if (
2497 ! isset( $comment->comment_approved ) ||
2498 ( '1' !== $comment->comment_approved && ! $is_note ) ) {
2499 return false;
2500 }
2501
2502 return wp_notify_postauthor( $comment_id );
2503}
2504
2505/**
2506 * Send a notification to the post author when a new note is added via the REST API.
2507 *
2508 * @since 6.9.0
2509 *
2510 * @param WP_Comment $comment The comment object.
2511 */
2512function wp_new_comment_via_rest_notify_postauthor( $comment ) {
2513 if ( $comment instanceof WP_Comment && 'note' === $comment->comment_type ) {
2514 wp_new_comment_notify_postauthor( (int) $comment->comment_ID );
2515 }
2516}
2517
2518/**
2519 * Sets the status of a comment.
2520 *
2521 * The {@see 'wp_set_comment_status'} action is called after the comment is handled.
2522 * If the comment status is not in the list, then false is returned.
2523 *
2524 * @since 1.0.0
2525 *
2526 * @global wpdb $wpdb WordPress database abstraction object.
2527 *
2528 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
2529 * @param string $comment_status New comment status, either 'hold', 'approve', 'spam', or 'trash'.
2530 * @param bool $wp_error Whether to return a WP_Error object if there is a failure. Default false.
2531 * @return bool|WP_Error True on success, false or WP_Error on failure.
2532 */
2533function wp_set_comment_status( $comment_id, $comment_status, $wp_error = false ) {
2534 global $wpdb;
2535
2536 switch ( $comment_status ) {
2537 case 'hold':
2538 case '0':
2539 $status = '0';
2540 break;
2541 case 'approve':
2542 case '1':
2543 $status = '1';
2544 add_action( 'wp_set_comment_status', 'wp_new_comment_notify_postauthor' );
2545 break;
2546 case 'spam':
2547 $status = 'spam';
2548 break;
2549 case 'trash':
2550 $status = 'trash';
2551 break;
2552 default:
2553 return false;
2554 }
2555
2556 $comment_old = clone get_comment( $comment_id );
2557
2558 if ( ! $wpdb->update( $wpdb->comments, array( 'comment_approved' => $status ), array( 'comment_ID' => $comment_old->comment_ID ) ) ) {
2559 if ( $wp_error ) {
2560 return new WP_Error( 'db_update_error', __( 'Could not update comment status.' ), $wpdb->last_error );
2561 } else {
2562 return false;
2563 }
2564 }
2565
2566 clean_comment_cache( $comment_old->comment_ID );
2567
2568 $comment = get_comment( $comment_old->comment_ID );
2569
2570 /**
2571 * Fires immediately after transitioning a comment's status from one to another in the database
2572 * and removing the comment from the object cache, but prior to all status transition hooks.
2573 *
2574 * @since 1.5.0
2575 *
2576 * @param string $comment_id Comment ID as a numeric string.
2577 * @param string $comment_status Current comment status. Possible values include
2578 * 'hold', '0', 'approve', '1', 'spam', and 'trash'.
2579 */
2580 do_action( 'wp_set_comment_status', $comment->comment_ID, $comment_status );
2581
2582 wp_transition_comment_status( $comment_status, $comment_old->comment_approved, $comment );
2583
2584 wp_update_comment_count( $comment->comment_post_ID );
2585
2586 return true;
2587}
2588
2589/**
2590 * Updates an existing comment in the database.
2591 *
2592 * Filters the comment and makes sure certain fields are valid before updating.
2593 *
2594 * @since 2.0.0
2595 * @since 4.9.0 Add updating comment meta during comment update.
2596 * @since 5.5.0 The `$wp_error` parameter was added.
2597 * @since 5.5.0 The return values for an invalid comment or post ID
2598 * were changed to false instead of 0.
2599 *
2600 * @global wpdb $wpdb WordPress database abstraction object.
2601 *
2602 * @param array $commentarr Contains information on the comment.
2603 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
2604 * @return int|false|WP_Error The value 1 if the comment was updated, 0 if not updated.
2605 * False or a WP_Error object on failure.
2606 */
2607function wp_update_comment( $commentarr, $wp_error = false ) {
2608 global $wpdb;
2609
2610 // First, get all of the original fields.
2611 $comment = get_comment( $commentarr['comment_ID'], ARRAY_A );
2612
2613 if ( empty( $comment ) ) {
2614 if ( $wp_error ) {
2615 return new WP_Error( 'invalid_comment_id', __( 'Invalid comment ID.' ) );
2616 } else {
2617 return false;
2618 }
2619 }
2620
2621 // Make sure that the comment post ID is valid (if specified).
2622 if ( ! empty( $commentarr['comment_post_ID'] ) && ! get_post( $commentarr['comment_post_ID'] ) ) {
2623 if ( $wp_error ) {
2624 return new WP_Error( 'invalid_post_id', __( 'Invalid post ID.' ) );
2625 } else {
2626 return false;
2627 }
2628 }
2629
2630 $filter_comment = false;
2631 if ( ! has_filter( 'pre_comment_content', 'wp_filter_kses' ) ) {
2632 $filter_comment = ! user_can( isset( $comment['user_id'] ) ? $comment['user_id'] : 0, 'unfiltered_html' );
2633 }
2634
2635 if ( $filter_comment ) {
2636 add_filter( 'pre_comment_content', 'wp_filter_kses' );
2637 }
2638
2639 // Escape data pulled from DB.
2640 $comment = wp_slash( $comment );
2641
2642 $old_status = $comment['comment_approved'];
2643
2644 // Merge old and new fields with new fields overwriting old ones.
2645 $commentarr = array_merge( $comment, $commentarr );
2646
2647 $commentarr = wp_filter_comment( $commentarr );
2648
2649 if ( $filter_comment ) {
2650 remove_filter( 'pre_comment_content', 'wp_filter_kses' );
2651 }
2652
2653 // Now extract the merged array.
2654 $data = wp_unslash( $commentarr );
2655
2656 /**
2657 * Filters the comment content before it is updated in the database.
2658 *
2659 * @since 1.5.0
2660 *
2661 * @param string $comment_content The comment data.
2662 */
2663 $data['comment_content'] = apply_filters( 'comment_save_pre', $data['comment_content'] );
2664
2665 $data['comment_date_gmt'] = get_gmt_from_date( $data['comment_date'] );
2666
2667 if ( ! isset( $data['comment_approved'] ) ) {
2668 $data['comment_approved'] = 1;
2669 } elseif ( 'hold' === $data['comment_approved'] ) {
2670 $data['comment_approved'] = 0;
2671 } elseif ( 'approve' === $data['comment_approved'] ) {
2672 $data['comment_approved'] = 1;
2673 }
2674
2675 $comment_id = $data['comment_ID'];
2676 $comment_post_id = $data['comment_post_ID'];
2677
2678 /**
2679 * Filters the comment data immediately before it is updated in the database.
2680 *
2681 * Note: data being passed to the filter is already unslashed.
2682 *
2683 * @since 4.7.0
2684 * @since 5.5.0 Returning a WP_Error value from the filter will short-circuit comment update
2685 * and allow skipping further processing.
2686 *
2687 * @param array|WP_Error $data The new, processed comment data, or WP_Error.
2688 * @param array $comment The old, unslashed comment data.
2689 * @param array $commentarr The new, raw comment data.
2690 */
2691 $data = apply_filters( 'wp_update_comment_data', $data, $comment, $commentarr );
2692
2693 // Do not carry on on failure.
2694 if ( is_wp_error( $data ) ) {
2695 if ( $wp_error ) {
2696 return $data;
2697 } else {
2698 return false;
2699 }
2700 }
2701
2702 $keys = array(
2703 'comment_post_ID',
2704 'comment_author',
2705 'comment_author_email',
2706 'comment_author_url',
2707 'comment_author_IP',
2708 'comment_date',
2709 'comment_date_gmt',
2710 'comment_content',
2711 'comment_karma',
2712 'comment_approved',
2713 'comment_agent',
2714 'comment_type',
2715 'comment_parent',
2716 'user_id',
2717 );
2718
2719 $data = wp_array_slice_assoc( $data, $keys );
2720
2721 $result = $wpdb->update( $wpdb->comments, $data, array( 'comment_ID' => $comment_id ) );
2722
2723 if ( false === $result ) {
2724 if ( $wp_error ) {
2725 return new WP_Error( 'db_update_error', __( 'Could not update comment in the database.' ), $wpdb->last_error );
2726 } else {
2727 return false;
2728 }
2729 }
2730
2731 // If metadata is provided, store it.
2732 if ( isset( $commentarr['comment_meta'] ) && is_array( $commentarr['comment_meta'] ) ) {
2733 foreach ( $commentarr['comment_meta'] as $meta_key => $meta_value ) {
2734 update_comment_meta( $comment_id, $meta_key, $meta_value );
2735 }
2736 }
2737
2738 clean_comment_cache( $comment_id );
2739 wp_update_comment_count( $comment_post_id );
2740
2741 /**
2742 * Fires immediately after a comment is updated in the database.
2743 *
2744 * The hook also fires immediately before comment status transition hooks are fired.
2745 *
2746 * @since 1.2.0
2747 * @since 4.6.0 Added the `$data` parameter.
2748 *
2749 * @param int $comment_id The comment ID.
2750 * @param array $data Comment data.
2751 */
2752 do_action( 'edit_comment', $comment_id, $data );
2753
2754 $comment = get_comment( $comment_id );
2755
2756 wp_transition_comment_status( $comment->comment_approved, $old_status, $comment );
2757
2758 return $result;
2759}
2760
2761/**
2762 * Determines whether to defer comment counting.
2763 *
2764 * When setting $defer to true, all post comment counts will not be updated
2765 * until $defer is set to false. When $defer is set to false, then all
2766 * previously deferred updated post comment counts will then be automatically
2767 * updated without having to call wp_update_comment_count() after.
2768 *
2769 * @since 2.5.0
2770 *
2771 * @param bool $defer
2772 * @return bool
2773 */
2774function wp_defer_comment_counting( $defer = null ) {
2775 static $_defer = false;
2776
2777 if ( is_bool( $defer ) ) {
2778 $_defer = $defer;
2779 // Flush any deferred counts.
2780 if ( ! $defer ) {
2781 wp_update_comment_count( null, true );
2782 }
2783 }
2784
2785 return $_defer;
2786}
2787
2788/**
2789 * Updates the comment count for post(s).
2790 *
2791 * When $do_deferred is false (is by default) and the comments have been set to
2792 * be deferred, the post_id will be added to a queue, which will be updated at a
2793 * later date and only updated once per post ID.
2794 *
2795 * If the comments have not be set up to be deferred, then the post will be
2796 * updated. When $do_deferred is set to true, then all previous deferred post
2797 * IDs will be updated along with the current $post_id.
2798 *
2799 * @since 2.1.0
2800 *
2801 * @see wp_update_comment_count_now() For what could cause a false return value
2802 *
2803 * @param int|null $post_id Post ID.
2804 * @param bool $do_deferred Optional. Whether to process previously deferred
2805 * post comment counts. Default false.
2806 * @return bool|void True on success, false on failure or if post with ID does
2807 * not exist.
2808 */
2809function wp_update_comment_count( $post_id, $do_deferred = false ) {
2810 static $_deferred = array();
2811
2812 if ( empty( $post_id ) && ! $do_deferred ) {
2813 return false;
2814 }
2815
2816 if ( $do_deferred ) {
2817 $_deferred = array_unique( $_deferred );
2818 foreach ( $_deferred as $i => $_post_id ) {
2819 wp_update_comment_count_now( $_post_id );
2820 unset( $_deferred[ $i ] );
2821 /** @todo Move this outside of the foreach and reset $_deferred to an array instead */
2822 }
2823 }
2824
2825 if ( wp_defer_comment_counting() ) {
2826 $_deferred[] = $post_id;
2827 return true;
2828 } elseif ( $post_id ) {
2829 return wp_update_comment_count_now( $post_id );
2830 }
2831}
2832
2833/**
2834 * Updates the comment count for the post.
2835 *
2836 * @since 2.5.0
2837 *
2838 * @global wpdb $wpdb WordPress database abstraction object.
2839 *
2840 * @param int $post_id Post ID
2841 * @return bool True on success, false if the post does not exist.
2842 */
2843function wp_update_comment_count_now( $post_id ) {
2844 global $wpdb;
2845
2846 $post_id = (int) $post_id;
2847
2848 if ( ! $post_id ) {
2849 return false;
2850 }
2851
2852 wp_cache_delete( 'comments-0', 'counts' );
2853 wp_cache_delete( "comments-{$post_id}", 'counts' );
2854
2855 $post = get_post( $post_id );
2856
2857 if ( ! $post ) {
2858 return false;
2859 }
2860
2861 $old = (int) $post->comment_count;
2862
2863 /**
2864 * Filters a post's comment count before it is updated in the database.
2865 *
2866 * @since 4.5.0
2867 *
2868 * @param int|null $new The new comment count. Default null.
2869 * @param int $old The old comment count.
2870 * @param int $post_id Post ID.
2871 */
2872 $new = apply_filters( 'pre_wp_update_comment_count_now', null, $old, $post_id );
2873
2874 if ( is_null( $new ) ) {
2875 $new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type != 'note'", $post_id ) );
2876 } else {
2877 $new = (int) $new;
2878 }
2879
2880 $wpdb->update( $wpdb->posts, array( 'comment_count' => $new ), array( 'ID' => $post_id ) );
2881
2882 clean_post_cache( $post );
2883
2884 /**
2885 * Fires immediately after a post's comment count is updated in the database.
2886 *
2887 * @since 2.3.0
2888 *
2889 * @param int $post_id Post ID.
2890 * @param int $new The new comment count.
2891 * @param int $old The old comment count.
2892 */
2893 do_action( 'wp_update_comment_count', $post_id, $new, $old );
2894
2895 /** This action is documented in wp-includes/post.php */
2896 do_action( "edit_post_{$post->post_type}", $post_id, $post );
2897
2898 /** This action is documented in wp-includes/post.php */
2899 do_action( 'edit_post', $post_id, $post );
2900
2901 return true;
2902}
2903
2904//
2905// Ping and trackback functions.
2906//
2907
2908/**
2909 * Finds a pingback server URI based on the given URL.
2910 *
2911 * Checks the HTML for the rel="pingback" link and X-Pingback headers. It does
2912 * a check for the X-Pingback headers first and returns that, if available.
2913 * The check for the rel="pingback" has more overhead than just the header.
2914 *
2915 * @since 1.5.0
2916 *
2917 * @param string $url URL to ping.
2918 * @param string $deprecated Not Used.
2919 * @return string|false String containing URI on success, false on failure.
2920 */
2921function discover_pingback_server_uri( $url, $deprecated = '' ) {
2922 if ( ! empty( $deprecated ) ) {
2923 _deprecated_argument( __FUNCTION__, '2.7.0' );
2924 }
2925
2926 $pingback_str_dquote = 'rel="pingback"';
2927 $pingback_str_squote = 'rel=\'pingback\'';
2928
2929 /** @todo Should use Filter Extension or custom preg_match instead. */
2930 $parsed_url = parse_url( $url );
2931
2932 if ( ! isset( $parsed_url['host'] ) ) { // Not a URL. This should never happen.
2933 return false;
2934 }
2935
2936 // Do not search for a pingback server on our own uploads.
2937 $uploads_dir = wp_get_upload_dir();
2938 if ( str_starts_with( $url, $uploads_dir['baseurl'] ) ) {
2939 return false;
2940 }
2941
2942 $response = wp_safe_remote_head(
2943 $url,
2944 array(
2945 'timeout' => 2,
2946 'httpversion' => '1.0',
2947 )
2948 );
2949
2950 if ( is_wp_error( $response ) ) {
2951 return false;
2952 }
2953
2954 if ( wp_remote_retrieve_header( $response, 'X-Pingback' ) ) {
2955 return wp_remote_retrieve_header( $response, 'X-Pingback' );
2956 }
2957
2958 // Not an (x)html, sgml, or xml page, no use going further.
2959 if ( preg_match( '#(image|audio|video|model)/#is', wp_remote_retrieve_header( $response, 'Content-Type' ) ) ) {
2960 return false;
2961 }
2962
2963 // Now do a GET since we're going to look in the HTML headers (and we're sure it's not a binary file).
2964 $response = wp_safe_remote_get(
2965 $url,
2966 array(
2967 'timeout' => 2,
2968 'httpversion' => '1.0',
2969 )
2970 );
2971
2972 if ( is_wp_error( $response ) ) {
2973 return false;
2974 }
2975
2976 $contents = wp_remote_retrieve_body( $response );
2977
2978 $pingback_link_offset_dquote = strpos( $contents, $pingback_str_dquote );
2979 $pingback_link_offset_squote = strpos( $contents, $pingback_str_squote );
2980
2981 if ( $pingback_link_offset_dquote || $pingback_link_offset_squote ) {
2982 $quote = ( $pingback_link_offset_dquote ) ? '"' : '\'';
2983 $pingback_link_offset = ( '"' === $quote ) ? $pingback_link_offset_dquote : $pingback_link_offset_squote;
2984 $pingback_href_pos = strpos( $contents, 'href=', $pingback_link_offset );
2985 $pingback_href_start = $pingback_href_pos + 6;
2986 $pingback_href_end = strpos( $contents, $quote, $pingback_href_start );
2987 $pingback_server_url_len = $pingback_href_end - $pingback_href_start;
2988 $pingback_server_url = substr( $contents, $pingback_href_start, $pingback_server_url_len );
2989
2990 // We may find rel="pingback" but an incomplete pingback URL.
2991 if ( $pingback_server_url_len > 0 ) { // We got it!
2992 return $pingback_server_url;
2993 }
2994 }
2995
2996 return false;
2997}
2998
2999/**
3000 * Performs all pingbacks, enclosures, trackbacks, and sends to pingback services.
3001 *
3002 * @since 2.1.0
3003 * @since 5.6.0 Introduced `do_all_pings` action hook for individual services.
3004 */
3005function do_all_pings() {
3006 /**
3007 * Fires immediately after the `do_pings` event to hook services individually.
3008 *
3009 * @since 5.6.0
3010 */
3011 do_action( 'do_all_pings' );
3012}
3013
3014/**
3015 * Performs all pingbacks.
3016 *
3017 * @since 5.6.0
3018 */
3019function do_all_pingbacks() {
3020 $pings = get_posts(
3021 array(
3022 'post_type' => get_post_types(),
3023 'suppress_filters' => false,
3024 'nopaging' => true,
3025 'meta_key' => '_pingme',
3026 'fields' => 'ids',
3027 )
3028 );
3029
3030 foreach ( $pings as $ping ) {
3031 delete_post_meta( $ping, '_pingme' );
3032 pingback( null, $ping );
3033 }
3034}
3035
3036/**
3037 * Performs all enclosures.
3038 *
3039 * @since 5.6.0
3040 */
3041function do_all_enclosures() {
3042 $enclosures = get_posts(
3043 array(
3044 'post_type' => get_post_types(),
3045 'suppress_filters' => false,
3046 'nopaging' => true,
3047 'meta_key' => '_encloseme',
3048 'fields' => 'ids',
3049 )
3050 );
3051
3052 foreach ( $enclosures as $enclosure ) {
3053 delete_post_meta( $enclosure, '_encloseme' );
3054 do_enclose( null, $enclosure );
3055 }
3056}
3057
3058/**
3059 * Performs all trackbacks.
3060 *
3061 * @since 5.6.0
3062 */
3063function do_all_trackbacks() {
3064 $trackbacks = get_posts(
3065 array(
3066 'post_type' => get_post_types(),
3067 'suppress_filters' => false,
3068 'nopaging' => true,
3069 'meta_key' => '_trackbackme',
3070 'fields' => 'ids',
3071 )
3072 );
3073
3074 foreach ( $trackbacks as $trackback ) {
3075 delete_post_meta( $trackback, '_trackbackme' );
3076 do_trackbacks( $trackback );
3077 }
3078}
3079
3080/**
3081 * Performs trackbacks.
3082 *
3083 * @since 1.5.0
3084 * @since 4.7.0 `$post` can be a WP_Post object.
3085 *
3086 * @global wpdb $wpdb WordPress database abstraction object.
3087 *
3088 * @param int|WP_Post $post Post ID or object to do trackbacks on.
3089 * @return void|false Returns false on failure.
3090 */
3091function do_trackbacks( $post ) {
3092 global $wpdb;
3093
3094 $post = get_post( $post );
3095
3096 if ( ! $post ) {
3097 return false;
3098 }
3099
3100 $to_ping = get_to_ping( $post );
3101 $pinged = get_pung( $post );
3102
3103 if ( empty( $to_ping ) ) {
3104 $wpdb->update( $wpdb->posts, array( 'to_ping' => '' ), array( 'ID' => $post->ID ) );
3105 return;
3106 }
3107
3108 if ( empty( $post->post_excerpt ) ) {
3109 /** This filter is documented in wp-includes/post-template.php */
3110 $excerpt = apply_filters( 'the_content', $post->post_content, $post->ID );
3111 } else {
3112 /** This filter is documented in wp-includes/post-template.php */
3113 $excerpt = apply_filters( 'the_excerpt', $post->post_excerpt );
3114 }
3115
3116 $excerpt = str_replace( ']]>', ']]&gt;', $excerpt );
3117 $excerpt = wp_html_excerpt( $excerpt, 252, '&#8230;' );
3118
3119 /** This filter is documented in wp-includes/post-template.php */
3120 $post_title = apply_filters( 'the_title', $post->post_title, $post->ID );
3121 $post_title = strip_tags( $post_title );
3122
3123 foreach ( (array) $to_ping as $tb_ping ) {
3124 $tb_ping = trim( $tb_ping );
3125 if ( ! in_array( $tb_ping, $pinged, true ) ) {
3126 trackback( $tb_ping, $post_title, $excerpt, $post->ID );
3127 $pinged[] = $tb_ping;
3128 } else {
3129 $wpdb->query(
3130 $wpdb->prepare(
3131 "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d",
3132 $tb_ping,
3133 $post->ID
3134 )
3135 );
3136 }
3137 }
3138}
3139
3140/**
3141 * Sends pings to all of the ping site services.
3142 *
3143 * @since 1.2.0
3144 *
3145 * @param int $post_id Post ID.
3146 * @return int Same post ID as provided.
3147 */
3148function generic_ping( $post_id = 0 ) {
3149 $services = get_option( 'ping_sites' );
3150
3151 $services = explode( "\n", $services );
3152 foreach ( (array) $services as $service ) {
3153 $service = trim( $service );
3154 if ( '' !== $service ) {
3155 weblog_ping( $service );
3156 }
3157 }
3158
3159 return $post_id;
3160}
3161
3162/**
3163 * Pings back the links found in a post.
3164 *
3165 * @since 0.71
3166 * @since 4.7.0 `$post` can be a WP_Post object.
3167 * @since 6.8.0 Returns an array of pingback statuses indexed by link.
3168 *
3169 * @param string $content Post content to check for links. If empty will retrieve from post.
3170 * @param int|WP_Post $post Post ID or object.
3171 * @return array<string, bool> An array of pingback statuses indexed by link.
3172 */
3173function pingback( $content, $post ) {
3174 require_once ABSPATH . WPINC . '/class-IXR.php';
3175 require_once ABSPATH . WPINC . '/class-wp-http-ixr-client.php';
3176
3177 // Original code by Mort (http://mort.mine.nu:8080).
3178 $post_links = array();
3179
3180 $post = get_post( $post );
3181
3182 if ( ! $post ) {
3183 return array();
3184 }
3185
3186 $pung = get_pung( $post );
3187
3188 if ( empty( $content ) ) {
3189 $content = $post->post_content;
3190 }
3191
3192 /*
3193 * Step 1.
3194 * Parsing the post, external links (if any) are stored in the $post_links array.
3195 */
3196 $post_links_temp = wp_extract_urls( $content );
3197
3198 $ping_status = array();
3199 /*
3200 * Step 2.
3201 * Walking through the links array.
3202 * First we get rid of links pointing to sites, not to specific files.
3203 * Example:
3204 * http://dummy-weblog.org
3205 * http://dummy-weblog.org/
3206 * http://dummy-weblog.org/post.php
3207 * We don't wanna ping first and second types, even if they have a valid <link/>.
3208 */
3209 foreach ( (array) $post_links_temp as $link_test ) {
3210 // If we haven't pung it already and it isn't a link to itself.
3211 if ( ! in_array( $link_test, $pung, true ) && ( url_to_postid( $link_test ) !== $post->ID )
3212 // Also, let's never ping local attachments.
3213 && ! is_local_attachment( $link_test )
3214 ) {
3215 $test = parse_url( $link_test );
3216 if ( $test ) {
3217 if ( isset( $test['query'] ) ) {
3218 $post_links[] = $link_test;
3219 } elseif ( isset( $test['path'] ) && ( '/' !== $test['path'] ) && ( '' !== $test['path'] ) ) {
3220 $post_links[] = $link_test;
3221 }
3222 }
3223 }
3224 }
3225
3226 $post_links = array_unique( $post_links );
3227
3228 /**
3229 * Fires just before pinging back links found in a post.
3230 *
3231 * @since 2.0.0
3232 *
3233 * @param string[] $post_links Array of link URLs to be checked (passed by reference).
3234 * @param string[] $pung Array of link URLs already pinged (passed by reference).
3235 * @param int $post_id The post ID.
3236 */
3237 do_action_ref_array( 'pre_ping', array( &$post_links, &$pung, $post->ID ) );
3238
3239 foreach ( (array) $post_links as $pagelinkedto ) {
3240 $pingback_server_url = discover_pingback_server_uri( $pagelinkedto );
3241
3242 if ( $pingback_server_url ) {
3243 // Allow an additional 60 seconds for each pingback to complete.
3244 if ( function_exists( 'set_time_limit' ) ) {
3245 set_time_limit( 60 );
3246 }
3247
3248 // Now, the RPC call.
3249 $pagelinkedfrom = get_permalink( $post );
3250
3251 // Using a timeout of 3 seconds should be enough to cover slow servers.
3252 $client = new WP_HTTP_IXR_Client( $pingback_server_url );
3253 $client->timeout = 3;
3254 /**
3255 * Filters the user agent sent when pinging-back a URL.
3256 *
3257 * @since 2.9.0
3258 *
3259 * @param string $concat_useragent The user agent concatenated with ' -- WordPress/'
3260 * and the WordPress version.
3261 * @param string $useragent The useragent.
3262 * @param string $pingback_server_url The server URL being linked to.
3263 * @param string $pagelinkedto URL of page linked to.
3264 * @param string $pagelinkedfrom URL of page linked from.
3265 */
3266 $client->useragent = apply_filters( 'pingback_useragent', $client->useragent . ' -- WordPress/' . get_bloginfo( 'version' ), $client->useragent, $pingback_server_url, $pagelinkedto, $pagelinkedfrom );
3267 // When set to true, this outputs debug messages by itself.
3268 $client->debug = false;
3269
3270 $status = $client->query( 'pingback.ping', $pagelinkedfrom, $pagelinkedto );
3271
3272 if ( $status // Ping registered.
3273 || ( isset( $client->error->code ) && 48 === $client->error->code ) // Already registered.
3274 ) {
3275 add_ping( $post, $pagelinkedto );
3276 }
3277 $ping_status[ $pagelinkedto ] = $status;
3278 }
3279 }
3280
3281 return $ping_status;
3282}
3283
3284/**
3285 * Checks whether blog is public before returning sites.
3286 *
3287 * @since 2.1.0
3288 *
3289 * @param mixed $sites Will return if blog is public, will not return if not public.
3290 * @return mixed Empty string if blog is not public, returns $sites, if site is public.
3291 */
3292function privacy_ping_filter( $sites ) {
3293 if ( '0' !== get_option( 'blog_public' ) ) {
3294 return $sites;
3295 } else {
3296 return '';
3297 }
3298}
3299
3300/**
3301 * Sends a Trackback.
3302 *
3303 * Updates database when sending trackback to prevent duplicates.
3304 *
3305 * @since 0.71
3306 *
3307 * @global wpdb $wpdb WordPress database abstraction object.
3308 *
3309 * @param string $trackback_url URL to send trackbacks.
3310 * @param string $title Title of post.
3311 * @param string $excerpt Excerpt of post.
3312 * @param int $post_id Post ID.
3313 * @return int|false|void Database query from update.
3314 */
3315function trackback( $trackback_url, $title, $excerpt, $post_id ) {
3316 global $wpdb;
3317
3318 if ( empty( $trackback_url ) ) {
3319 return;
3320 }
3321
3322 $options = array();
3323 $options['timeout'] = 10;
3324 $options['body'] = array(
3325 'title' => $title,
3326 'url' => get_permalink( $post_id ),
3327 'blog_name' => get_option( 'blogname' ),
3328 'excerpt' => $excerpt,
3329 );
3330
3331 $response = wp_safe_remote_post( $trackback_url, $options );
3332
3333 if ( is_wp_error( $response ) ) {
3334 return;
3335 }
3336
3337 $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', %s) WHERE ID = %d", $trackback_url, $post_id ) );
3338 return $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $trackback_url, $post_id ) );
3339}
3340
3341/**
3342 * Sends a pingback.
3343 *
3344 * @since 1.2.0
3345 *
3346 * @param string $server Host of blog to connect to.
3347 * @param string $path Path to send the ping.
3348 */
3349function weblog_ping( $server = '', $path = '' ) {
3350 require_once ABSPATH . WPINC . '/class-IXR.php';
3351 require_once ABSPATH . WPINC . '/class-wp-http-ixr-client.php';
3352
3353 // Using a timeout of 3 seconds should be enough to cover slow servers.
3354 $client = new WP_HTTP_IXR_Client( $server, ( ( ! strlen( trim( $path ) ) || ( '/' === $path ) ) ? false : $path ) );
3355 $client->timeout = 3;
3356 $client->useragent .= ' -- WordPress/' . get_bloginfo( 'version' );
3357
3358 // When set to true, this outputs debug messages by itself.
3359 $client->debug = false;
3360 $home = trailingslashit( home_url() );
3361 if ( ! $client->query( 'weblogUpdates.extendedPing', get_option( 'blogname' ), $home, get_bloginfo( 'rss2_url' ) ) ) { // Then try a normal ping.
3362 $client->query( 'weblogUpdates.ping', get_option( 'blogname' ), $home );
3363 }
3364}
3365
3366/**
3367 * Default filter attached to pingback_ping_source_uri to validate the pingback's Source URI.
3368 *
3369 * @since 3.5.1
3370 *
3371 * @see wp_http_validate_url()
3372 *
3373 * @param string $source_uri
3374 * @return string
3375 */
3376function pingback_ping_source_uri( $source_uri ) {
3377 return (string) wp_http_validate_url( $source_uri );
3378}
3379
3380/**
3381 * Default filter attached to xmlrpc_pingback_error.
3382 *
3383 * Returns a generic pingback error code unless the error code is 48,
3384 * which reports that the pingback is already registered.
3385 *
3386 * @since 3.5.1
3387 *
3388 * @link https://www.hixie.ch/specs/pingback/pingback#TOC3
3389 *
3390 * @param IXR_Error $ixr_error
3391 * @return IXR_Error
3392 */
3393function xmlrpc_pingback_error( $ixr_error ) {
3394 if ( 48 === $ixr_error->code ) {
3395 return $ixr_error;
3396 }
3397 return new IXR_Error( 0, '' );
3398}
3399
3400//
3401// Cache.
3402//
3403
3404/**
3405 * Removes a comment from the object cache.
3406 *
3407 * @since 2.3.0
3408 *
3409 * @param int|array $ids Comment ID or an array of comment IDs to remove from cache.
3410 */
3411function clean_comment_cache( $ids ) {
3412 $comment_ids = (array) $ids;
3413 wp_cache_delete_multiple( $comment_ids, 'comment' );
3414 foreach ( $comment_ids as $id ) {
3415 /**
3416 * Fires immediately after a comment has been removed from the object cache.
3417 *
3418 * @since 4.5.0
3419 *
3420 * @param int $id Comment ID.
3421 */
3422 do_action( 'clean_comment_cache', $id );
3423 }
3424
3425 wp_cache_set_comments_last_changed();
3426}
3427
3428/**
3429 * Updates the comment cache of given comments.
3430 *
3431 * Will add the comments in $comments to the cache. If comment ID already exists
3432 * in the comment cache then it will not be updated. The comment is added to the
3433 * cache using the comment group with the key using the ID of the comments.
3434 *
3435 * @since 2.3.0
3436 * @since 4.4.0 Introduced the `$update_meta_cache` parameter.
3437 *
3438 * @param WP_Comment[] $comments Array of comment objects
3439 * @param bool $update_meta_cache Whether to update commentmeta cache. Default true.
3440 */
3441function update_comment_cache( $comments, $update_meta_cache = true ) {
3442 $data = array();
3443 foreach ( (array) $comments as $comment ) {
3444 $data[ $comment->comment_ID ] = $comment;
3445 }
3446 wp_cache_add_multiple( $data, 'comment' );
3447
3448 if ( $update_meta_cache ) {
3449 // Avoid `wp_list_pluck()` in case `$comments` is passed by reference.
3450 $comment_ids = array();
3451 foreach ( $comments as $comment ) {
3452 $comment_ids[] = $comment->comment_ID;
3453 }
3454 update_meta_cache( 'comment', $comment_ids );
3455 }
3456}
3457
3458/**
3459 * Adds any comments from the given IDs to the cache that do not already exist in cache.
3460 *
3461 * @since 4.4.0
3462 * @since 6.1.0 This function is no longer marked as "private".
3463 * @since 6.3.0 Use wp_lazyload_comment_meta() for lazy-loading of comment meta.
3464 *
3465 * @see update_comment_cache()
3466 * @global wpdb $wpdb WordPress database abstraction object.
3467 *
3468 * @param int[] $comment_ids Array of comment IDs.
3469 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
3470 */
3471function _prime_comment_caches( $comment_ids, $update_meta_cache = true ) {
3472 global $wpdb;
3473
3474 $non_cached_ids = _get_non_cached_ids( $comment_ids, 'comment' );
3475 if ( ! empty( $non_cached_ids ) ) {
3476 $fresh_comments = $wpdb->get_results( sprintf( "SELECT $wpdb->comments.* FROM $wpdb->comments WHERE comment_ID IN (%s)", implode( ',', array_map( 'intval', $non_cached_ids ) ) ) );
3477
3478 update_comment_cache( $fresh_comments, false );
3479 }
3480
3481 if ( $update_meta_cache ) {
3482 wp_lazyload_comment_meta( $comment_ids );
3483 }
3484}
3485
3486//
3487// Internal.
3488//
3489
3490/**
3491 * Closes comments on old posts on the fly, without any extra DB queries. Hooked to the_posts.
3492 *
3493 * @since 2.7.0
3494 * @access private
3495 *
3496 * @param WP_Post[] $posts Array of post objects.
3497 * @param WP_Query $query Query object.
3498 * @return WP_Post[]
3499 */
3500function _close_comments_for_old_posts( $posts, $query ) {
3501 if ( empty( $posts ) || ! $query->is_singular() || ! get_option( 'close_comments_for_old_posts' ) ) {
3502 return $posts;
3503 }
3504
3505 /**
3506 * Filters the list of post types to automatically close comments for.
3507 *
3508 * @since 3.2.0
3509 *
3510 * @param string[] $post_types An array of post type names.
3511 */
3512 $post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) );
3513 if ( ! in_array( $posts[0]->post_type, $post_types, true ) ) {
3514 return $posts;
3515 }
3516
3517 $days_old = (int) get_option( 'close_comments_days_old' );
3518 if ( ! $days_old ) {
3519 return $posts;
3520 }
3521
3522 if ( time() - strtotime( $posts[0]->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) ) {
3523 $posts[0]->comment_status = 'closed';
3524 $posts[0]->ping_status = 'closed';
3525 }
3526
3527 return $posts;
3528}
3529
3530/**
3531 * Closes comments on an old post. Hooked to comments_open and pings_open.
3532 *
3533 * @since 2.7.0
3534 * @access private
3535 *
3536 * @param bool $open Comments open or closed.
3537 * @param int $post_id Post ID.
3538 * @return bool $open
3539 */
3540function _close_comments_for_old_post( $open, $post_id ) {
3541 if ( ! $open ) {
3542 return $open;
3543 }
3544
3545 if ( ! get_option( 'close_comments_for_old_posts' ) ) {
3546 return $open;
3547 }
3548
3549 $days_old = (int) get_option( 'close_comments_days_old' );
3550 if ( ! $days_old ) {
3551 return $open;
3552 }
3553
3554 $post = get_post( $post_id );
3555
3556 /** This filter is documented in wp-includes/comment.php */
3557 $post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) );
3558 if ( ! in_array( $post->post_type, $post_types, true ) ) {
3559 return $open;
3560 }
3561
3562 // Undated drafts should not show up as comments closed.
3563 if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) {
3564 return $open;
3565 }
3566
3567 if ( time() - strtotime( $post->post_date_gmt ) > ( $days_old * DAY_IN_SECONDS ) ) {
3568 return false;
3569 }
3570
3571 return $open;
3572}
3573
3574/**
3575 * Handles the submission of a comment, usually posted to wp-comments-post.php via a comment form.
3576 *
3577 * This function expects unslashed data, as opposed to functions such as `wp_new_comment()` which
3578 * expect slashed data.
3579 *
3580 * @since 4.4.0
3581 *
3582 * @param array $comment_data {
3583 * Comment data.
3584 *
3585 * @type string|int $comment_post_ID The ID of the post that relates to the comment.
3586 * @type string $author The name of the comment author.
3587 * @type string $email The comment author email address.
3588 * @type string $url The comment author URL.
3589 * @type string $comment The content of the comment.
3590 * @type string|int $comment_parent The ID of this comment's parent, if any. Default 0.
3591 * @type string $_wp_unfiltered_html_comment The nonce value for allowing unfiltered HTML.
3592 * }
3593 * @return WP_Comment|WP_Error A WP_Comment object on success, a WP_Error object on failure.
3594 */
3595function wp_handle_comment_submission( $comment_data ) {
3596 $comment_post_id = 0;
3597 $comment_author = '';
3598 $comment_author_email = '';
3599 $comment_author_url = '';
3600 $comment_content = '';
3601 $comment_parent = 0;
3602 $user_id = 0;
3603
3604 if ( isset( $comment_data['comment_post_ID'] ) ) {
3605 $comment_post_id = (int) $comment_data['comment_post_ID'];
3606 }
3607 if ( isset( $comment_data['author'] ) && is_string( $comment_data['author'] ) ) {
3608 $comment_author = trim( strip_tags( $comment_data['author'] ) );
3609 }
3610 if ( isset( $comment_data['email'] ) && is_string( $comment_data['email'] ) ) {
3611 $comment_author_email = trim( $comment_data['email'] );
3612 }
3613 if ( isset( $comment_data['url'] ) && is_string( $comment_data['url'] ) ) {
3614 $comment_author_url = trim( $comment_data['url'] );
3615 }
3616 if ( isset( $comment_data['comment'] ) && is_string( $comment_data['comment'] ) ) {
3617 $comment_content = trim( $comment_data['comment'] );
3618 }
3619 if ( isset( $comment_data['comment_parent'] ) ) {
3620 $comment_parent = absint( $comment_data['comment_parent'] );
3621 $comment_parent_object = get_comment( $comment_parent );
3622
3623 if (
3624 0 !== $comment_parent &&
3625 (
3626 ! $comment_parent_object instanceof WP_Comment ||
3627 0 === (int) $comment_parent_object->comment_approved
3628 )
3629 ) {
3630 /**
3631 * Fires when a comment reply is attempted to an unapproved comment.
3632 *
3633 * @since 6.2.0
3634 *
3635 * @param int $comment_post_id Post ID.
3636 * @param int $comment_parent Parent comment ID.
3637 */
3638 do_action( 'comment_reply_to_unapproved_comment', $comment_post_id, $comment_parent );
3639
3640 return new WP_Error( 'comment_reply_to_unapproved_comment', __( 'Sorry, replies to unapproved comments are not allowed.' ), 403 );
3641 }
3642 }
3643
3644 $post = get_post( $comment_post_id );
3645
3646 if ( empty( $post->comment_status ) ) {
3647
3648 /**
3649 * Fires when a comment is attempted on a post that does not exist.
3650 *
3651 * @since 1.5.0
3652 *
3653 * @param int $comment_post_id Post ID.
3654 */
3655 do_action( 'comment_id_not_found', $comment_post_id );
3656
3657 return new WP_Error( 'comment_id_not_found' );
3658
3659 }
3660
3661 // get_post_status() will get the parent status for attachments.
3662 $status = get_post_status( $post );
3663
3664 if ( ( 'private' === $status ) && ! current_user_can( 'read_post', $comment_post_id ) ) {
3665 return new WP_Error( 'comment_id_not_found' );
3666 }
3667
3668 $status_obj = get_post_status_object( $status );
3669
3670 if ( ! comments_open( $comment_post_id ) ) {
3671
3672 /**
3673 * Fires when a comment is attempted on a post that has comments closed.
3674 *
3675 * @since 1.5.0
3676 *
3677 * @param int $comment_post_id Post ID.
3678 */
3679 do_action( 'comment_closed', $comment_post_id );
3680
3681 return new WP_Error( 'comment_closed', __( 'Sorry, comments are closed for this item.' ), 403 );
3682
3683 } elseif ( 'trash' === $status ) {
3684
3685 /**
3686 * Fires when a comment is attempted on a trashed post.
3687 *
3688 * @since 2.9.0
3689 *
3690 * @param int $comment_post_id Post ID.
3691 */
3692 do_action( 'comment_on_trash', $comment_post_id );
3693
3694 return new WP_Error( 'comment_on_trash' );
3695
3696 } elseif ( ! $status_obj->public && ! $status_obj->private ) {
3697
3698 /**
3699 * Fires when a comment is attempted on a post in draft mode.
3700 *
3701 * @since 1.5.1
3702 *
3703 * @param int $comment_post_id Post ID.
3704 */
3705 do_action( 'comment_on_draft', $comment_post_id );
3706
3707 if ( current_user_can( 'read_post', $comment_post_id ) ) {
3708 return new WP_Error( 'comment_on_draft', __( 'Sorry, comments are not allowed for this item.' ), 403 );
3709 } else {
3710 return new WP_Error( 'comment_on_draft' );
3711 }
3712 } elseif ( post_password_required( $comment_post_id ) ) {
3713
3714 /**
3715 * Fires when a comment is attempted on a password-protected post.
3716 *
3717 * @since 2.9.0
3718 *
3719 * @param int $comment_post_id Post ID.
3720 */
3721 do_action( 'comment_on_password_protected', $comment_post_id );
3722
3723 return new WP_Error( 'comment_on_password_protected' );
3724
3725 } else {
3726 /**
3727 * Fires before a comment is posted.
3728 *
3729 * @since 2.8.0
3730 *
3731 * @param int $comment_post_id Post ID.
3732 */
3733 do_action( 'pre_comment_on_post', $comment_post_id );
3734 }
3735
3736 // If the user is logged in.
3737 $user = wp_get_current_user();
3738 if ( $user->exists() ) {
3739 if ( empty( $user->display_name ) ) {
3740 $user->display_name = $user->user_login;
3741 }
3742
3743 $comment_author = $user->display_name;
3744 $comment_author_email = $user->user_email;
3745 $comment_author_url = $user->user_url;
3746 $user_id = $user->ID;
3747
3748 if ( current_user_can( 'unfiltered_html' ) ) {
3749 if ( ! isset( $comment_data['_wp_unfiltered_html_comment'] )
3750 || ! wp_verify_nonce( $comment_data['_wp_unfiltered_html_comment'], 'unfiltered-html-comment_' . $comment_post_id )
3751 ) {
3752 kses_remove_filters(); // Start with a clean slate.
3753 kses_init_filters(); // Set up the filters.
3754 remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
3755 add_filter( 'pre_comment_content', 'wp_filter_kses' );
3756 }
3757 }
3758 } else {
3759 if ( get_option( 'comment_registration' ) ) {
3760 return new WP_Error( 'not_logged_in', __( 'Sorry, you must be logged in to comment.' ), 403 );
3761 }
3762 }
3763
3764 $comment_type = 'comment';
3765
3766 if ( get_option( 'require_name_email' ) && ! $user->exists() ) {
3767 if ( '' === $comment_author_email || '' === $comment_author ) {
3768 return new WP_Error( 'require_name_email', __( '<strong>Error:</strong> Please fill the required fields.' ), 200 );
3769 } elseif ( ! is_email( $comment_author_email ) ) {
3770 return new WP_Error( 'require_valid_email', __( '<strong>Error:</strong> Please enter a valid email address.' ), 200 );
3771 }
3772 }
3773
3774 $commentdata = array(
3775 'comment_post_ID' => $comment_post_id,
3776 );
3777
3778 $commentdata += compact(
3779 'comment_author',
3780 'comment_author_email',
3781 'comment_author_url',
3782 'comment_content',
3783 'comment_type',
3784 'comment_parent',
3785 'user_id'
3786 );
3787
3788 /**
3789 * Filters whether an empty comment should be allowed.
3790 *
3791 * @since 5.1.0
3792 *
3793 * @param bool $allow_empty_comment Whether to allow empty comments. Default false.
3794 * @param array $commentdata Array of comment data to be sent to wp_insert_comment().
3795 */
3796 $allow_empty_comment = apply_filters( 'allow_empty_comment', false, $commentdata );
3797 if ( '' === $comment_content && ! $allow_empty_comment ) {
3798 return new WP_Error( 'require_valid_comment', __( '<strong>Error:</strong> Please type your comment text.' ), 200 );
3799 }
3800
3801 $check_max_lengths = wp_check_comment_data_max_lengths( $commentdata );
3802 if ( is_wp_error( $check_max_lengths ) ) {
3803 return $check_max_lengths;
3804 }
3805
3806 $comment_id = wp_new_comment( wp_slash( $commentdata ), true );
3807 if ( is_wp_error( $comment_id ) ) {
3808 return $comment_id;
3809 }
3810
3811 if ( ! $comment_id ) {
3812 return new WP_Error( 'comment_save_error', __( '<strong>Error:</strong> The comment could not be saved. Please try again later.' ), 500 );
3813 }
3814
3815 return get_comment( $comment_id );
3816}
3817
3818/**
3819 * Registers the personal data exporter for comments.
3820 *
3821 * @since 4.9.6
3822 *
3823 * @param array[] $exporters An array of personal data exporters.
3824 * @return array[] An array of personal data exporters.
3825 */
3826function wp_register_comment_personal_data_exporter( $exporters ) {
3827 $exporters['wordpress-comments'] = array(
3828 'exporter_friendly_name' => __( 'WordPress Comments' ),
3829 'callback' => 'wp_comments_personal_data_exporter',
3830 );
3831
3832 return $exporters;
3833}
3834
3835/**
3836 * Finds and exports personal data associated with an email address from the comments table.
3837 *
3838 * @since 4.9.6
3839 *
3840 * @param string $email_address The comment author email address.
3841 * @param int $page Comment page number.
3842 * @return array {
3843 * An array of personal data.
3844 *
3845 * @type array[] $data An array of personal data arrays.
3846 * @type bool $done Whether the exporter is finished.
3847 * }
3848 */
3849function wp_comments_personal_data_exporter( $email_address, $page = 1 ) {
3850 // Limit us to 500 comments at a time to avoid timing out.
3851 $number = 500;
3852 $page = (int) $page;
3853
3854 $data_to_export = array();
3855
3856 $comments = get_comments(
3857 array(
3858 'author_email' => $email_address,
3859 'number' => $number,
3860 'paged' => $page,
3861 'orderby' => 'comment_ID',
3862 'order' => 'ASC',
3863 'update_comment_meta_cache' => false,
3864 )
3865 );
3866
3867 $comment_prop_to_export = array(
3868 'comment_author' => __( 'Comment Author' ),
3869 'comment_author_email' => __( 'Comment Author Email' ),
3870 'comment_author_url' => __( 'Comment Author URL' ),
3871 'comment_author_IP' => __( 'Comment Author IP' ),
3872 'comment_agent' => __( 'Comment Author User Agent' ),
3873 'comment_date' => __( 'Comment Date' ),
3874 'comment_content' => __( 'Comment Content' ),
3875 'comment_link' => __( 'Comment URL' ),
3876 );
3877
3878 foreach ( (array) $comments as $comment ) {
3879 $comment_data_to_export = array();
3880
3881 foreach ( $comment_prop_to_export as $key => $name ) {
3882 $value = '';
3883
3884 switch ( $key ) {
3885 case 'comment_author':
3886 case 'comment_author_email':
3887 case 'comment_author_url':
3888 case 'comment_author_IP':
3889 case 'comment_agent':
3890 case 'comment_date':
3891 $value = $comment->{$key};
3892 break;
3893
3894 case 'comment_content':
3895 $value = get_comment_text( $comment->comment_ID );
3896 break;
3897
3898 case 'comment_link':
3899 $value = get_comment_link( $comment->comment_ID );
3900 $value = sprintf(
3901 '<a href="%s" target="_blank">%s</a>',
3902 esc_url( $value ),
3903 esc_html( $value )
3904 );
3905 break;
3906 }
3907
3908 if ( ! empty( $value ) ) {
3909 $comment_data_to_export[] = array(
3910 'name' => $name,
3911 'value' => $value,
3912 );
3913 }
3914 }
3915
3916 $data_to_export[] = array(
3917 'group_id' => 'comments',
3918 'group_label' => __( 'Comments' ),
3919 'group_description' => __( 'User&#8217;s comment data.' ),
3920 'item_id' => "comment-{$comment->comment_ID}",
3921 'data' => $comment_data_to_export,
3922 );
3923 }
3924
3925 $done = count( $comments ) < $number;
3926
3927 return array(
3928 'data' => $data_to_export,
3929 'done' => $done,
3930 );
3931}
3932
3933/**
3934 * Registers the personal data eraser for comments.
3935 *
3936 * @since 4.9.6
3937 *
3938 * @param array $erasers An array of personal data erasers.
3939 * @return array An array of personal data erasers.
3940 */
3941function wp_register_comment_personal_data_eraser( $erasers ) {
3942 $erasers['wordpress-comments'] = array(
3943 'eraser_friendly_name' => __( 'WordPress Comments' ),
3944 'callback' => 'wp_comments_personal_data_eraser',
3945 );
3946
3947 return $erasers;
3948}
3949
3950/**
3951 * Erases personal data associated with an email address from the comments table.
3952 *
3953 * @since 4.9.6
3954 *
3955 * @global wpdb $wpdb WordPress database abstraction object.
3956 *
3957 * @param string $email_address The comment author email address.
3958 * @param int $page Comment page number.
3959 * @return array {
3960 * Data removal results.
3961 *
3962 * @type bool $items_removed Whether items were actually removed.
3963 * @type bool $items_retained Whether items were retained.
3964 * @type string[] $messages An array of messages to add to the personal data export file.
3965 * @type bool $done Whether the eraser is finished.
3966 * }
3967 */
3968function wp_comments_personal_data_eraser( $email_address, $page = 1 ) {
3969 global $wpdb;
3970
3971 if ( empty( $email_address ) ) {
3972 return array(
3973 'items_removed' => false,
3974 'items_retained' => false,
3975 'messages' => array(),
3976 'done' => true,
3977 );
3978 }
3979
3980 // Limit us to 500 comments at a time to avoid timing out.
3981 $number = 500;
3982 $page = (int) $page;
3983 $items_removed = false;
3984 $items_retained = false;
3985
3986 $comments = get_comments(
3987 array(
3988 'author_email' => $email_address,
3989 'number' => $number,
3990 'paged' => $page,
3991 'orderby' => 'comment_ID',
3992 'order' => 'ASC',
3993 'include_unapproved' => true,
3994 )
3995 );
3996
3997 /* translators: Name of a comment's author after being anonymized. */
3998 $anon_author = __( 'Anonymous' );
3999 $messages = array();
4000
4001 foreach ( (array) $comments as $comment ) {
4002 $anonymized_comment = array();
4003 $anonymized_comment['comment_agent'] = '';
4004 $anonymized_comment['comment_author'] = $anon_author;
4005 $anonymized_comment['comment_author_email'] = '';
4006 $anonymized_comment['comment_author_IP'] = wp_privacy_anonymize_data( 'ip', $comment->comment_author_IP );
4007 $anonymized_comment['comment_author_url'] = '';
4008 $anonymized_comment['user_id'] = 0;
4009
4010 $comment_id = (int) $comment->comment_ID;
4011
4012 /**
4013 * Filters whether to anonymize the comment.
4014 *
4015 * @since 4.9.6
4016 *
4017 * @param bool|string $anon_message Whether to apply the comment anonymization (bool) or a custom
4018 * message (string). Default true.
4019 * @param WP_Comment $comment WP_Comment object.
4020 * @param array $anonymized_comment Anonymized comment data.
4021 */
4022 $anon_message = apply_filters( 'wp_anonymize_comment', true, $comment, $anonymized_comment );
4023
4024 if ( true !== $anon_message ) {
4025 if ( $anon_message && is_string( $anon_message ) ) {
4026 $messages[] = esc_html( $anon_message );
4027 } else {
4028 /* translators: %d: Comment ID. */
4029 $messages[] = sprintf( __( 'Comment %d contains personal data but could not be anonymized.' ), $comment_id );
4030 }
4031
4032 $items_retained = true;
4033
4034 continue;
4035 }
4036
4037 $args = array(
4038 'comment_ID' => $comment_id,
4039 );
4040
4041 $updated = $wpdb->update( $wpdb->comments, $anonymized_comment, $args );
4042
4043 if ( $updated ) {
4044 $items_removed = true;
4045 clean_comment_cache( $comment_id );
4046 } else {
4047 $items_retained = true;
4048 }
4049 }
4050
4051 $done = count( $comments ) < $number;
4052
4053 return array(
4054 'items_removed' => $items_removed,
4055 'items_retained' => $items_retained,
4056 'messages' => $messages,
4057 'done' => $done,
4058 );
4059}
4060
4061/**
4062 * Sets the last changed time for the 'comment' cache group.
4063 *
4064 * @since 5.0.0
4065 */
4066function wp_cache_set_comments_last_changed() {
4067 wp_cache_set_last_changed( 'comment' );
4068}
4069
4070/**
4071 * Updates the comment type for a batch of comments.
4072 *
4073 * @since 5.5.0
4074 *
4075 * @global wpdb $wpdb WordPress database abstraction object.
4076 */
4077function _wp_batch_update_comment_type() {
4078 global $wpdb;
4079
4080 $lock_name = 'update_comment_type.lock';
4081
4082 // Try to lock.
4083 $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
4084
4085 if ( ! $lock_result ) {
4086 $lock_result = get_option( $lock_name );
4087
4088 // Bail if we were unable to create a lock, or if the existing lock is still valid.
4089 if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
4090 wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_update_comment_type_batch' );
4091 return;
4092 }
4093 }
4094
4095 // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
4096 update_option( $lock_name, time() );
4097
4098 // Check if there's still an empty comment type.
4099 $empty_comment_type = $wpdb->get_var(
4100 "SELECT comment_ID FROM $wpdb->comments
4101 WHERE comment_type = ''
4102 LIMIT 1"
4103 );
4104
4105 // No empty comment type, we're done here.
4106 if ( ! $empty_comment_type ) {
4107 update_option( 'finished_updating_comment_type', true );
4108 delete_option( $lock_name );
4109 return;
4110 }
4111
4112 // Empty comment type found? We'll need to run this script again.
4113 wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_update_comment_type_batch' );
4114
4115 /**
4116 * Filters the comment batch size for updating the comment type.
4117 *
4118 * @since 5.5.0
4119 *
4120 * @param int $comment_batch_size The comment batch size. Default 100.
4121 */
4122 $comment_batch_size = (int) apply_filters( 'wp_update_comment_type_batch_size', 100 );
4123
4124 // Get the IDs of the comments to update.
4125 $comment_ids = $wpdb->get_col(
4126 $wpdb->prepare(
4127 "SELECT comment_ID
4128 FROM {$wpdb->comments}
4129 WHERE comment_type = ''
4130 ORDER BY comment_ID DESC
4131 LIMIT %d",
4132 $comment_batch_size
4133 )
4134 );
4135
4136 if ( $comment_ids ) {
4137 $comment_id_list = implode( ',', $comment_ids );
4138
4139 // Update the `comment_type` field value to be `comment` for the next batch of comments.
4140 $wpdb->query(
4141 "UPDATE {$wpdb->comments}
4142 SET comment_type = 'comment'
4143 WHERE comment_type = ''
4144 AND comment_ID IN ({$comment_id_list})" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
4145 );
4146
4147 // Make sure to clean the comment cache.
4148 clean_comment_cache( $comment_ids );
4149 }
4150
4151 delete_option( $lock_name );
4152}
4153
4154/**
4155 * In order to avoid the _wp_batch_update_comment_type() job being accidentally removed,
4156 * check that it's still scheduled while we haven't finished updating comment types.
4157 *
4158 * @ignore
4159 * @since 5.5.0
4160 */
4161function _wp_check_for_scheduled_update_comment_type() {
4162 if ( ! get_option( 'finished_updating_comment_type' ) && ! wp_next_scheduled( 'wp_update_comment_type_batch' ) ) {
4163 wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_update_comment_type_batch' );
4164 }
4165}
4166
4167/**
4168 * Register initial note status meta.
4169 *
4170 * @since 6.9.0
4171 */
4172function wp_create_initial_comment_meta() {
4173 register_meta(
4174 'comment',
4175 '_wp_note_status',
4176 array(
4177 'type' => 'string',
4178 'description' => __( 'Note resolution status' ),
4179 'single' => true,
4180 'show_in_rest' => array(
4181 'schema' => array(
4182 'type' => 'string',
4183 'enum' => array( 'resolved', 'reopen' ),
4184 ),
4185 ),
4186 'auth_callback' => function ( $allowed, $meta_key, $object_id ) {
4187 return current_user_can( 'edit_comment', $object_id );
4188 },
4189 )
4190 );
4191}
4192
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1