PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 12.2.2
Jetpack – WP Security, Backup, Speed, & Growth v12.2.2
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / comments / base.php

base.php in Jetpack – WP Security, Backup, Speed, & Growth 12.2.2, at modules/comments/base.php

330 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Jetpack comments base file - where the code shared between WP.com Highlander and Jetpack Highlander is defined
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
9
10 /**
11 * All the code shared between WP.com Highlander and Jetpack Highlander
12 */
13 class Highlander_Comments_Base {
14
15 /**
16 * Constructor
17 */
18 public function __construct() {
19 $this->setup_globals();
20 $this->setup_actions();
21 $this->setup_filters();
22 }
23
24 /**
25 * Set any global variables or class variables
26 *
27 * @since JetpackComments (1.4)
28 */
29 protected function setup_globals() {}
30
31 /**
32 * Setup actions for methods in this class
33 *
34 * @since JetpackComments (1.4)
35 */
36 protected function setup_actions() {
37 // Before a comment is posted.
38 add_action( 'pre_comment_on_post', array( $this, 'allow_logged_out_user_to_comment_as_external' ) );
39
40 // After a comment is posted.
41 add_action( 'comment_post', array( $this, 'set_comment_cookies' ) );
42 }
43
44 /**
45 * Setup filters for methods in this class
46 *
47 * @since JetpackComments (1.4)
48 */
49 protected function setup_filters() {
50 add_filter( 'comments_array', array( $this, 'comments_array' ) );
51 add_filter( 'preprocess_comment', array( $this, 'allow_logged_in_user_to_comment_as_guest' ), 0 );
52 }
53
54 /**
55 * Is this a Highlander POST request?
56 * Optionally restrict to one or more credentials slug (facebook, twitter, ...)
57 *
58 * @param mixed ...$args Comments credentials slugs.
59 * @return false|string false if it's not a Highlander POST request. The matching credentials slug if it is.
60 */
61 public function is_highlander_comment_post( ...$args ) {
62
63 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification should happen in Jetpack_Comments::pre_comment_on_post(). Internal ref for details: p1645643468937519/1645189749.180299-slack-C02HQGKMFJ8
64 if ( empty( $_POST['hc_post_as'] ) ) {
65 return false;
66 }
67 $hc_post_as = wp_unslash( $_POST['hc_post_as'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized here by comparing against known values.
68 // phpcs:enable WordPress.Security.NonceVerification.Missing
69
70 if ( $args ) {
71 foreach ( $args as $id_source ) {
72 if ( $id_source === $hc_post_as ) {
73 return $id_source;
74 }
75 }
76 return false;
77 }
78 return is_string( $hc_post_as ) && in_array( $hc_post_as, $this->id_sources, true ) ? $hc_post_as : false;
79 }
80
81 /**
82 * Signs an array of scalars with the self-hosted blog's Jetpack Token
83 *
84 * If parameter values are not scalars a WP_Error is returned, otherwise a keyed hash value is returned using the HMAC method.
85 *
86 * @param array $parameters Comment parameters.
87 * @param string $key Key used for generating the HMAC variant of the message digest.
88 * @return string HMAC
89 */
90 public static function sign_remote_comment_parameters( $parameters, $key ) {
91 unset(
92 $parameters['sig'], // Don't sign the signature.
93 $parameters['replytocom'] // This parameter is unsigned - it changes dynamically as the comment form moves from parent comment to parent comment.
94 );
95
96 ksort( $parameters );
97
98 $signing = array();
99 foreach ( $parameters as $k => $v ) {
100 if ( ! is_scalar( $v ) ) {
101 return new WP_Error( 'invalid_input', __( 'Invalid request', 'jetpack' ), array( 'status' => 400 ) );
102 }
103
104 $signing[] = "{$k}={$v}";
105 }
106
107 return hash_hmac( 'sha1', implode( ':', $signing ), $key );
108 }
109
110 /**
111 * Adds comment author email and whether the comment is approved to the comments array
112 *
113 * After commenting as a guest while logged in, the user needs to see both:
114 * ( user_id = blah AND comment_approved = 0 )
115 * and ( comment_author_email = blah AND comment_approved = 0 )
116 * Core only does the first since the user is logged in, so this adds the second to the comments array.
117 *
118 * @param array $comments All comment data.
119 * @return array A modified array of comment data.
120 */
121 public function comments_array( $comments ) {
122 global $wpdb, $post;
123
124 $commenter = $this->get_current_commenter();
125
126 if ( ! $commenter['user_id'] ) {
127 return $comments;
128 }
129
130 if ( ! $commenter['comment_author'] ) {
131 return $comments;
132 }
133
134 $in_moderation_comments = $wpdb->get_results(
135 $wpdb->prepare(
136 "SELECT * FROM `$wpdb->comments` WHERE `comment_post_ID` = %d AND `user_id` = 0 AND `comment_author` = %s AND `comment_author_email` = %s AND `comment_approved` = '0' ORDER BY `comment_date_gmt` /* Highlander_Comments_Base::comments_array() */",
137 $post->ID,
138 wp_specialchars_decode( $commenter['comment_author'], ENT_QUOTES ),
139 $commenter['comment_author_email']
140 )
141 );
142
143 if ( ! $in_moderation_comments ) {
144 return $comments;
145 }
146
147 // @todo ZOMG this is a bad idea
148 $comments = array_merge( $comments, $in_moderation_comments );
149 usort( $comments, array( $this, 'sort_comments_by_comment_date_gmt' ) );
150
151 return $comments;
152 }
153
154 /**
155 * Comment sort comparator: comment_date_gmt
156 *
157 * @since JetpackComments (1.4)
158 * @param object $a The first comment to compare dates with.
159 * @param object $b The second comment to compare dates with.
160 * @return int
161 */
162 public function sort_comments_by_comment_date_gmt( $a, $b ) {
163 if ( $a->comment_date_gmt === $b->comment_date_gmt ) {
164 return 0;
165 }
166
167 return $a->comment_date_gmt < $b->comment_date_gmt ? -1 : 1;
168 }
169
170 /**
171 * Get the current commenter's information from their cookie
172 *
173 * @since JetpackComments (1.4)
174 * @return array Commenters information from cookie
175 */
176 protected function get_current_commenter() {
177 // Defaults.
178 $user_id = 0;
179 $comment_author = '';
180 $comment_author_email = '';
181 $comment_author_url = '';
182
183 if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
184 $comment_author = sanitize_text_field( wp_unslash( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) );
185 }
186
187 if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
188 $comment_author_email = sanitize_email( wp_unslash( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) );
189 }
190
191 if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
192 $comment_author_url = esc_url_raw( wp_unslash( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) );
193 }
194
195 if ( is_user_logged_in() ) {
196 $user = wp_get_current_user();
197 $user_id = $user->ID;
198 }
199
200 return compact( 'comment_author', 'comment_author_email', 'comment_author_url', 'user_id' );
201 }
202
203 /**
204 * Allows a logged out user to leave a comment as a facebook or twitter credentialed user.
205 * Overrides WordPress' core comment_registration option to treat these commenters as "registered" (verified) users.
206 *
207 * @since JetpackComments (1.4)
208 */
209 public function allow_logged_out_user_to_comment_as_external() {
210 if ( ! $this->is_highlander_comment_post( 'facebook', 'twitter' ) ) {
211 return;
212 }
213
214 add_filter( 'pre_option_comment_registration', '__return_zero' );
215 add_filter( 'pre_option_require_name_email', '__return_zero' );
216 }
217
218 /**
219 * Allow a logged in user to post as a guest, FB, or twitter credentialed request.
220 * Bypasses WordPress' core overrides that force a logged in user to comment as that user.
221 * Respects comment_registration option.
222 *
223 * @since JetpackComments (1.4)
224 * @param array $comment_data All data for a specific comment.
225 * @return array Modified comment data, or an error if the required fields or a valid email address are not entered.
226 */
227 public function allow_logged_in_user_to_comment_as_guest( $comment_data ) {
228 // Bail if user registration is allowed.
229 if ( get_option( 'comment_registration' ) ) {
230 return $comment_data;
231 }
232
233 // Bail if user is not logged in or not a post request.
234 if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) || ! is_user_logged_in() ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- simple comparison
235 return $comment_data;
236 }
237
238 // Bail if this is not a guest or external service credentialed request.
239 if ( ! $this->is_highlander_comment_post( 'guest', 'facebook', 'twitter' ) ) {
240 return $comment_data;
241 }
242
243 $user = wp_get_current_user();
244
245 foreach ( array(
246 'comment_author' => 'display_name',
247 'comment_author_email' => 'user_email',
248 'comment_author_url' => 'user_url',
249 ) as $comment_field => $user_field ) {
250 if ( addslashes( $user->$user_field ) !== $comment_data[ $comment_field ] ) {
251 return $comment_data; // some other plugin already did something funky.
252 }
253 }
254
255 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification should happen in Jetpack_Comments::pre_comment_on_post()
256 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization too
257 if ( get_option( 'require_name_email' ) ) {
258 if ( isset( $_POST['email'] ) && 6 > strlen( wp_unslash( $_POST['email'] ) ) || empty( $_POST['author'] ) ) {
259 wp_die( esc_html__( 'Error: please fill the required fields (name, email).', 'jetpack' ), 400 );
260 } elseif ( ! isset( $_POST['email'] ) || ! is_email( wp_unslash( $_POST['email'] ) ) ) {
261 wp_die( esc_html__( 'Error: please enter a valid email address.', 'jetpack' ), 400 );
262 }
263 }
264
265 $author_change = false;
266 foreach ( array(
267 'comment_author' => 'author',
268 'comment_author_email' => 'email',
269 'comment_author_url' => 'url',
270 ) as $comment_field => $post_field ) {
271 if ( ( ! isset( $_POST[ $post_field ] ) || $comment_data[ $comment_field ] !== $_POST[ $post_field ] ) && 'url' !== $post_field ) {
272 $author_change = true;
273 }
274 $comment_data[ $comment_field ] = isset( $_POST[ $post_field ] ) ? wp_unslash( $_POST[ $post_field ] ) : null;
275 }
276 // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
277
278 // Mark as guest comment if name or email were changed.
279 if ( $author_change ) {
280 $comment_data['user_ID'] = 0;
281 $comment_data['user_id'] = $comment_data['user_ID'];
282 }
283
284 return $comment_data;
285 }
286
287 /**
288 * Set the comment cookies or bail if comment is invalid
289 *
290 * @since JetpackComments (1.4)
291 * @param int $comment_id The comment ID.
292 */
293 public function set_comment_cookies( $comment_id ) {
294 // Get comment and bail if it's invalid somehow.
295 $comment = get_comment( $comment_id );
296 if ( empty( $comment ) || is_wp_error( $comment ) ) {
297 return;
298 }
299
300 $id_source = $this->is_highlander_comment_post();
301 if ( empty( $id_source ) ) {
302 return;
303 }
304
305 // Set comment author cookies.
306 // phpcs:ignore WordPress.WP.CapitalPDangit
307 if ( ( 'wordpress' !== $id_source ) && is_user_logged_in() ) {
308 /** This filter is already documented in core/wp-includes/comment-functions.php */
309 $comment_cookie_lifetime = apply_filters( 'comment_cookie_lifetime', 30000000 );
310 setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
311 setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
312 setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
313 }
314 }
315
316 /**
317 * Get an avatar from Photon
318 *
319 * @since JetpackComments (1.4)
320 * @param string $url The avatar URL.
321 * @param int $size The avatar size.
322 * @return string
323 */
324 protected function photon_avatar( $url, $size ) {
325 $size = (int) $size;
326
327 return Image_CDN_Core::cdn_url( $url, array( 'resize' => "$size,$size" ) );
328 }
329 }
330