PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9.1
Jetpack – WP Security, Backup, Speed, & Growth v15.9.1
16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / comments / base.php
jetpack / modules / comments Last commit date
subscription-modal-on-comment 7 months ago admin.php 7 months ago base.php 7 months ago comments.php 1 month ago
base.php
364 lines
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 * ID sources.
16 *
17 * @var array
18 */
19 public $id_sources;
20
21 /**
22 * The default comment scheme, if set.
23 *
24 * @var ?string
25 */
26 public $default_color_scheme;
27
28 /**
29 * Constructor
30 */
31 public function __construct() {
32 $this->setup_globals();
33 $this->setup_actions();
34 $this->setup_filters();
35 }
36
37 /**
38 * Set any global variables or class variables
39 *
40 * @since 1.4
41 */
42 protected function setup_globals() {}
43
44 /**
45 * Setup actions for methods in this class
46 *
47 * @since 1.4
48 */
49 protected function setup_actions() {
50 // Before a comment is posted.
51 add_action( 'pre_comment_on_post', array( $this, 'allow_logged_out_user_to_comment_as_external' ) );
52
53 // After a comment is posted.
54 add_action( 'comment_post', array( $this, 'set_comment_cookies' ) );
55 }
56
57 /**
58 * Setup filters for methods in this class
59 *
60 * @since 1.4
61 */
62 protected function setup_filters() {
63 add_filter( 'comments_array', array( $this, 'comments_array' ) );
64 add_filter( 'preprocess_comment', array( $this, 'allow_logged_in_user_to_comment_as_guest' ), 0 );
65 }
66
67 /**
68 * Is this a Highlander POST request?
69 * Optionally restrict to one or more credentials slug (facebook, ...)
70 *
71 * @param mixed ...$args Comments credentials slugs.
72 * @return false|string false if it's not a Highlander POST request. The matching credentials slug if it is.
73 */
74 public function is_highlander_comment_post( ...$args ) {
75
76 // 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
77 if ( empty( $_POST['hc_post_as'] ) ) {
78 return false;
79 }
80 $hc_post_as = wp_unslash( $_POST['hc_post_as'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized here by comparing against known values.
81 // phpcs:enable WordPress.Security.NonceVerification.Missing
82
83 if ( $args ) {
84 foreach ( $args as $id_source ) {
85 if ( $id_source === $hc_post_as ) {
86 return $id_source;
87 }
88 }
89 return false;
90 }
91 return is_string( $hc_post_as ) && in_array( $hc_post_as, $this->id_sources, true ) ? $hc_post_as : false;
92 }
93
94 /**
95 * Signs an array of scalars with the self-hosted blog's Jetpack Token
96 *
97 * If parameter values are not scalars a WP_Error is returned, otherwise a keyed hash value is returned using the HMAC method.
98 *
99 * @param array $parameters Comment parameters.
100 * @param string $key Key used for generating the HMAC variant of the message digest.
101 * @return string HMAC
102 */
103 public static function sign_remote_comment_parameters( $parameters, $key ) {
104 unset(
105 $parameters['sig'], // Don't sign the signature.
106 $parameters['replytocom'] // This parameter is unsigned - it changes dynamically as the comment form moves from parent comment to parent comment.
107 );
108
109 ksort( $parameters );
110
111 $signing = array();
112 foreach ( $parameters as $k => $v ) {
113 if ( ! is_scalar( $v ) ) {
114 return new WP_Error( 'invalid_input', __( 'Invalid request', 'jetpack' ), array( 'status' => 400 ) );
115 }
116
117 $signing[] = "{$k}={$v}";
118 }
119
120 return hash_hmac( 'sha1', implode( ':', $signing ), $key );
121 }
122
123 /**
124 * Adds comment author email and whether the comment is approved to the comments array
125 *
126 * After commenting as a guest while logged in, the user needs to see both:
127 * ( user_id = blah AND comment_approved = 0 )
128 * and ( comment_author_email = blah AND comment_approved = 0 )
129 * Core only does the first since the user is logged in, so this adds the second to the comments array.
130 *
131 * @param array $comments All comment data.
132 * @return array A modified array of comment data.
133 */
134 public function comments_array( $comments ) {
135 global $wpdb, $post;
136
137 $commenter = $this->get_current_commenter();
138
139 if ( ! $commenter['user_id'] ) {
140 return $comments;
141 }
142
143 if ( ! $commenter['comment_author'] ) {
144 return $comments;
145 }
146
147 $in_moderation_comments = $wpdb->get_results(
148 $wpdb->prepare(
149 "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() */",
150 $post->ID,
151 wp_specialchars_decode( $commenter['comment_author'], ENT_QUOTES ),
152 $commenter['comment_author_email']
153 )
154 );
155
156 if ( ! $in_moderation_comments ) {
157 return $comments;
158 }
159
160 // @todo ZOMG this is a bad idea
161 $comments = array_merge( $comments, $in_moderation_comments );
162 usort( $comments, array( $this, 'sort_comments_by_comment_date_gmt' ) );
163
164 return $comments;
165 }
166
167 /**
168 * Comment sort comparator: comment_date_gmt
169 *
170 * @since 1.4
171 * @param object $a The first comment to compare dates with.
172 * @param object $b The second comment to compare dates with.
173 * @return int
174 */
175 public function sort_comments_by_comment_date_gmt( $a, $b ) {
176 return $a->comment_date_gmt <=> $b->comment_date_gmt;
177 }
178
179 /**
180 * Get the current commenter's information from their cookie
181 *
182 * @since 1.4
183 * @return array Commenters information from cookie
184 */
185 protected function get_current_commenter() {
186 // Defaults.
187 $user_id = 0;
188 $comment_author = '';
189 $comment_author_email = '';
190 $comment_author_url = '';
191
192 if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
193 $comment_author = sanitize_text_field( wp_unslash( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) );
194 }
195
196 if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
197 $comment_author_email = sanitize_email( wp_unslash( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) );
198 }
199
200 if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
201 $comment_author_url = esc_url_raw( wp_unslash( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) );
202 }
203
204 if ( is_user_logged_in() ) {
205 $user = wp_get_current_user();
206 $user_id = $user->ID;
207 }
208
209 return compact( 'comment_author', 'comment_author_email', 'comment_author_url', 'user_id' );
210 }
211
212 /**
213 * Allows a logged out user to leave a comment as a facebook/wp.com credentialed user.
214 * Overrides WordPress' core comment_registration option to treat these commenters as "registered" (verified) users.
215 *
216 * @since 1.4
217 */
218 public function allow_logged_out_user_to_comment_as_external() {
219 // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText
220 if ( ! $this->is_highlander_comment_post( 'facebook', 'wordpress' ) ) {
221 return;
222 }
223
224 add_filter( 'pre_option_comment_registration', '__return_zero' );
225 add_filter( 'pre_option_require_name_email', '__return_zero' );
226 }
227
228 /**
229 * Allow a logged in user to post as a guest, or FB credentialed request.
230 * Bypasses WordPress' core overrides that force a logged in user to comment as that user.
231 * Respects comment_registration option.
232 *
233 * @since 1.4
234 * @param array $comment_data All data for a specific comment.
235 * @return array Modified comment data, or an error if the required fields or a valid email address are not entered.
236 */
237 public function allow_logged_in_user_to_comment_as_guest( $comment_data ) {
238 // Bail if user registration is allowed.
239 if ( get_option( 'comment_registration' ) ) {
240 return $comment_data;
241 }
242
243 // Bail if user is not logged in or not a post request.
244 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
245 return $comment_data;
246 }
247
248 // Bail if this is not a guest or external service credentialed request.
249 if ( ! $this->is_highlander_comment_post( 'guest', 'facebook' ) ) {
250 return $comment_data;
251 }
252
253 $user = wp_get_current_user();
254
255 foreach ( array(
256 'comment_author' => 'display_name',
257 'comment_author_email' => 'user_email',
258 'comment_author_url' => 'user_url',
259 ) as $comment_field => $user_field ) {
260 if ( addslashes( $user->$user_field ) !== $comment_data[ $comment_field ] ) {
261 return $comment_data; // some other plugin already did something funky.
262 }
263 }
264
265 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification should happen in Jetpack_Comments::pre_comment_on_post()
266 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization too
267 if ( get_option( 'require_name_email' ) ) {
268 if ( isset( $_POST['email'] ) && 6 > strlen( wp_unslash( $_POST['email'] ) ) || empty( $_POST['author'] ) ) {
269 wp_die( esc_html__( 'Error: please fill the required fields (name, email).', 'jetpack' ), 400 );
270 } elseif ( ! isset( $_POST['email'] ) || ! is_email( wp_unslash( $_POST['email'] ) ) ) {
271 wp_die( esc_html__( 'Error: please enter a valid email address.', 'jetpack' ), 400 );
272 }
273 }
274
275 $author_change = false;
276 foreach ( array(
277 'comment_author' => 'author',
278 'comment_author_email' => 'email',
279 'comment_author_url' => 'url',
280 ) as $comment_field => $post_field ) {
281 if ( ( ! isset( $_POST[ $post_field ] ) || $comment_data[ $comment_field ] !== $_POST[ $post_field ] ) && 'url' !== $post_field ) {
282 $author_change = true;
283 }
284 $comment_data[ $comment_field ] = isset( $_POST[ $post_field ] ) ? wp_unslash( $_POST[ $post_field ] ) : null;
285 }
286 // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
287
288 // Mark as guest comment if name or email were changed.
289 if ( $author_change ) {
290 $comment_data['user_ID'] = 0;
291 $comment_data['user_id'] = $comment_data['user_ID'];
292 }
293
294 return $comment_data;
295 }
296
297 /**
298 * Set the comment cookies or bail if comment is invalid
299 *
300 * @since 1.4
301 * @param int $comment_id The comment ID.
302 */
303 public function set_comment_cookies( $comment_id ) {
304 // Get comment and bail if it's invalid somehow.
305 $comment = get_comment( $comment_id );
306 if ( empty( $comment ) || is_wp_error( $comment ) ) {
307 return;
308 }
309
310 $id_source = $this->is_highlander_comment_post();
311 if ( empty( $id_source ) ) {
312 return;
313 }
314
315 // Set comment author cookies.
316 // We don't set the cookies if they are logged in with WordPress.com because they already have a cookie set.
317 // phpcs:ignore WordPress.WP.CapitalPDangit
318 if ( 'wordpress' !== $id_source ) {
319 // phpcs:disable WordPress.Security.NonceVerification -- Nonce verification should happen in Jetpack_Comments::pre_comment_on_post().
320 $is_consenting_to_cookies = ( isset( $_POST['wp-comment-cookies-consent'] ) );
321
322 $cookie_options = array(
323 'expires' => time() + apply_filters( 'comment_cookie_lifetime', YEAR_IN_SECONDS ),
324 'path' => COOKIEPATH,
325 'domain' => COOKIE_DOMAIN,
326 'secure' => is_ssl(),
327 'httponly' => true,
328 );
329
330 // If there is no consent, remove any cookies that may have been set.
331 if ( ( 'guest' === $id_source ) && ! $is_consenting_to_cookies ) {
332 $cookie_options['expires'] = time() - YEAR_IN_SECONDS;
333 }
334
335 // Set samesite to None if the request is from Jetpack iframe.
336 // This is needed because it is considered third party.
337 if ( isset( $_REQUEST['for'] ) && 'jetpack' === $_REQUEST['for'] ) {
338 $cookie_options['samesite'] = 'None';
339 }
340 // phpcs:enable WordPress.Security.NonceVerification
341
342 // phpcs:disable Jetpack.Functions.SetCookie.MissingTrueHTTPOnly
343 isset( $comment->comment_author ) ? setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, $cookie_options ) : null;
344 isset( $comment->comment_author_email ) ? setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, $cookie_options ) : null;
345 isset( $comment->comment_author_url ) ? setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), $cookie_options ) : null;
346 // phpcs:enable Jetpack.Functions.SetCookie.MissingTrueHTTPOnly
347 }
348 }
349
350 /**
351 * Get an avatar from Photon
352 *
353 * @since 1.4
354 * @param string $url The avatar URL.
355 * @param int $size The avatar size.
356 * @return string
357 */
358 protected function photon_avatar( $url, $size ) {
359 $size = (int) $size;
360
361 return Image_CDN_Core::cdn_url( $url, array( 'resize' => "$size,$size" ) );
362 }
363 }
364