| 1 |
<?php |
| 2 |
/** |
| 3 |
* The comment form. |
| 4 |
* |
| 5 |
* @package automattic/jetpack-comments |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace Automattic\Jetpack\Comments; |
| 9 |
|
| 10 |
use Automattic\Jetpack\Assets; |
| 11 |
|
| 12 |
/** |
| 13 |
* Replaces the core comment form, and accepts what it submits. |
| 14 |
*/ |
| 15 |
class Comment_Form { |
| 16 |
|
| 17 |
const HANDLE = 'jetpack-comments'; |
| 18 |
const NONCE_ACTION = 'jetpack_comments_form'; |
| 19 |
const NONCE_NAME = 'jetpack_comments_form_nonce'; |
| 20 |
|
| 21 |
/** |
| 22 |
* Singleton instance. |
| 23 |
* |
| 24 |
* @var Comment_Form|null |
| 25 |
*/ |
| 26 |
private static $instance = null; |
| 27 |
|
| 28 |
/** |
| 29 |
* Whether the settings blob has been printed. |
| 30 |
* |
| 31 |
* @var bool |
| 32 |
*/ |
| 33 |
private $settings_printed = false; |
| 34 |
|
| 35 |
/** |
| 36 |
* The form defaults last seen, for the must-log-in branch, which core fires with no arguments. |
| 37 |
* |
| 38 |
* @var array |
| 39 |
*/ |
| 40 |
private $defaults = array(); |
| 41 |
|
| 42 |
/** |
| 43 |
* Register the form's hooks. Safe to call more than once. |
| 44 |
* |
| 45 |
* @return Comment_Form |
| 46 |
*/ |
| 47 |
public static function init() { |
| 48 |
if ( null === self::$instance ) { |
| 49 |
self::$instance = new self(); |
| 50 |
} |
| 51 |
|
| 52 |
return self::$instance; |
| 53 |
} |
| 54 |
|
| 55 |
/** |
| 56 |
* Take over the core comment form. |
| 57 |
*/ |
| 58 |
private function __construct() { |
| 59 |
add_filter( 'comment_form_fields', array( $this, 'comment_form_fields' ) ); |
| 60 |
add_filter( 'comment_form_logged_in', array( $this, 'comment_form_logged_in' ) ); |
| 61 |
add_filter( 'comment_form_defaults', array( $this, 'comment_form_defaults' ), 20 ); |
| 62 |
// Past 10, where Jetpack Subscriptions adds its checkboxes, so this sees them before replacing the field. |
| 63 |
add_filter( 'comment_form_submit_field', array( $this, 'render' ), 20, 2 ); |
| 64 |
add_action( 'comment_form_must_log_in_after', array( $this, 'render_must_log_in' ) ); |
| 65 |
add_filter( 'comment_reply_link', array( $this, 'comment_reply_link' ), 10, 4 ); |
| 66 |
add_action( 'wp_enqueue_scripts', array( $this, 'register_assets' ) ); |
| 67 |
add_action( 'pre_comment_on_post', array( $this, 'verify_nonce' ) ); |
| 68 |
} |
| 69 |
|
| 70 |
/** |
| 71 |
* Keep Reply moving the form when the site requires registration. |
| 72 |
* |
| 73 |
* @param string $reply_link Markup for the reply link. |
| 74 |
* @param array $args Reply link arguments. |
| 75 |
* @param \WP_Comment $comment Comment being replied to. |
| 76 |
* @param \WP_Post $post Post being commented on. |
| 77 |
* @return string |
| 78 |
*/ |
| 79 |
public function comment_reply_link( $reply_link, $args, $comment, $post ) { |
| 80 |
if ( ! get_option( 'comment_registration' ) || ! self::enabled_for_post_type() ) { |
| 81 |
return $reply_link; |
| 82 |
} |
| 83 |
|
| 84 |
$comment = get_comment( $comment ); |
| 85 |
$post = get_post( $post ); |
| 86 |
|
| 87 |
if ( ! $comment instanceof \WP_Comment || ! $post instanceof \WP_Post ) { |
| 88 |
return $reply_link; |
| 89 |
} |
| 90 |
|
| 91 |
$respond_id = esc_attr( $args['respond_id'] ); |
| 92 |
$reply_to = sprintf( $args['reply_to_text'], get_comment_author( $comment ) ); |
| 93 |
|
| 94 |
// A theme may put an icon inside its reply link. |
| 95 |
$reply_text_html = array( |
| 96 |
'svg' => array( |
| 97 |
'class' => true, |
| 98 |
'aria-hidden' => true, |
| 99 |
'aria-labelledby' => true, |
| 100 |
'role' => true, |
| 101 |
'xmlns' => true, |
| 102 |
'width' => true, |
| 103 |
'height' => true, |
| 104 |
'viewbox' => true, |
| 105 |
), |
| 106 |
'use' => array( |
| 107 |
'href' => true, |
| 108 |
'xlink:href' => true, |
| 109 |
), |
| 110 |
); |
| 111 |
|
| 112 |
$link = sprintf( |
| 113 |
'<a class="comment-reply-link" href="%s"%s onclick="return addComment.moveForm( \'%s-%d\', \'%d\', \'%s\', \'%d\' )">%s</a>', |
| 114 |
esc_url( add_query_arg( 'replytocom', $comment->comment_ID . '#' . $respond_id ) ), |
| 115 |
$args['show_reply_to_text'] ? '' : ' aria-label="' . esc_attr( $reply_to ) . '"', |
| 116 |
esc_attr( $args['add_below'] ), |
| 117 |
$comment->comment_ID, |
| 118 |
$comment->comment_ID, |
| 119 |
$respond_id, |
| 120 |
$post->ID, |
| 121 |
wp_kses( $args['show_reply_to_text'] ? $reply_to : $args['reply_text'], $reply_text_html ) |
| 122 |
); |
| 123 |
|
| 124 |
return wp_kses( $args['before'], wp_kses_allowed_html( 'post' ) ) |
| 125 |
. $link |
| 126 |
. wp_kses( $args['after'], wp_kses_allowed_html( 'post' ) ); |
| 127 |
} |
| 128 |
|
| 129 |
/** |
| 130 |
* Whether this form replaces core's for a post's type. |
| 131 |
* |
| 132 |
* @param int|null $post_id Post being commented on. Defaults to the current one. |
| 133 |
* @return bool |
| 134 |
*/ |
| 135 |
public static function enabled_for_post_type( $post_id = null ) { |
| 136 |
$post_type = $post_id ? get_post_type( $post_id ) : get_post_type(); |
| 137 |
|
| 138 |
/** This filter is documented in projects/plugins/jetpack/modules/comments/comments.php */ |
| 139 |
return (bool) apply_filters( 'jetpack_comment_form_enabled_for_' . $post_type, true ); |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Drop every field core would draw. |
| 144 |
* |
| 145 |
* @param array $fields Comment form fields, the textarea included. |
| 146 |
* @return array |
| 147 |
*/ |
| 148 |
public function comment_form_fields( $fields ) { |
| 149 |
return self::enabled_for_post_type() ? array() : $fields; |
| 150 |
} |
| 151 |
|
| 152 |
/** |
| 153 |
* Suppress core's logged-in line. |
| 154 |
* |
| 155 |
* @param string $logged_in_as The "logged in as" markup. |
| 156 |
* @return string |
| 157 |
*/ |
| 158 |
public function comment_form_logged_in( $logged_in_as ) { |
| 159 |
return self::enabled_for_post_type() ? '' : $logged_in_as; |
| 160 |
} |
| 161 |
|
| 162 |
/** |
| 163 |
* Set the form arguments the app reads back out. |
| 164 |
* |
| 165 |
* @param array $args Comment form arguments. |
| 166 |
* @return array |
| 167 |
*/ |
| 168 |
public function comment_form_defaults( $args ) { |
| 169 |
if ( ! self::enabled_for_post_type() ) { |
| 170 |
return $args; |
| 171 |
} |
| 172 |
|
| 173 |
$defaults = array( |
| 174 |
'logged_in_as' => '', |
| 175 |
'comment_notes_before' => '', |
| 176 |
'must_log_in' => '', |
| 177 |
'label_submit' => _x( 'Comment', 'verb', 'jetpack-comments' ), |
| 178 |
); |
| 179 |
|
| 180 |
$greeting = get_option( 'highlander_comment_form_prompt' ); |
| 181 |
if ( is_string( $greeting ) && $greeting !== '' ) { |
| 182 |
$defaults['title_reply'] = $greeting; |
| 183 |
} |
| 184 |
|
| 185 |
$this->defaults = array_merge( $args, $defaults ); |
| 186 |
|
| 187 |
return $this->defaults; |
| 188 |
} |
| 189 |
|
| 190 |
/** |
| 191 |
* Replace the submit field with the app. |
| 192 |
* |
| 193 |
* @param string $submit_field The submit field markup this replaces. |
| 194 |
* @param array $args Comment form arguments, after the theme's own. |
| 195 |
* @return string |
| 196 |
*/ |
| 197 |
public function render( $submit_field, $args = array() ) { |
| 198 |
if ( ! self::enabled_for_post_type() ) { |
| 199 |
return $submit_field; |
| 200 |
} |
| 201 |
|
| 202 |
// The subscribe checkboxes the host drew: Jetpack's in this field, WordPress.com's from its own |
| 203 |
// function. Drawn in the dialog under the host's names, so its gating and handlers still apply. |
| 204 |
$drawn = $submit_field; |
| 205 |
if ( function_exists( 'subscription_comment_form' ) ) { |
| 206 |
// Echoed and caught: its stub declares no return value. |
| 207 |
ob_start(); |
| 208 |
subscription_comment_form( self::post_id() ); |
| 209 |
$drawn .= (string) ob_get_clean(); |
| 210 |
} |
| 211 |
|
| 212 |
$labels = array( |
| 213 |
'subscribe_comments' => __( 'Notify me of new comments by email.', 'jetpack-comments' ), |
| 214 |
'subscribe' => __( 'Notify me of new comments by email.', 'jetpack-comments' ), |
| 215 |
'subscribe_blog' => sprintf( |
| 216 |
/* translators: %s is the site's name. */ |
| 217 |
__( 'Subscribe to keep up with %s.', 'jetpack-comments' ), |
| 218 |
get_bloginfo( 'name' ) |
| 219 |
), |
| 220 |
); |
| 221 |
|
| 222 |
$args['subscriptions'] = array(); |
| 223 |
foreach ( $labels as $name => $label ) { |
| 224 |
if ( preg_match( '/<input\b[^>]*\bname="' . $name . '"[^>]*>/', $drawn, $input ) ) { |
| 225 |
$args['subscriptions'][] = array( |
| 226 |
'name' => $name, |
| 227 |
'label' => $label, |
| 228 |
'checked' => false !== strpos( $input[0], 'checked' ), |
| 229 |
); |
| 230 |
} |
| 231 |
} |
| 232 |
|
| 233 |
// Fires after this filter, and would draw the subscribe options again below the form. |
| 234 |
remove_action( 'comment_form', 'subscription_comment_form' ); |
| 235 |
|
| 236 |
$this->enqueue_assets( $args ); |
| 237 |
|
| 238 |
return $this->markup( $args ); |
| 239 |
} |
| 240 |
|
| 241 |
/** |
| 242 |
* Draw the app, and a form to hold it, on the must-log-in branch. |
| 243 |
* |
| 244 |
* @return void |
| 245 |
*/ |
| 246 |
public function render_must_log_in() { |
| 247 |
if ( ! self::enabled_for_post_type() ) { |
| 248 |
return; |
| 249 |
} |
| 250 |
|
| 251 |
$args = $this->defaults; |
| 252 |
|
| 253 |
$this->enqueue_assets( $args ); |
| 254 |
|
| 255 |
printf( |
| 256 |
'<form action="%s" method="post" id="commentform" class="comment-form">%s</form>', |
| 257 |
esc_url( site_url( '/wp-comments-post.php' ) ), |
| 258 |
$this->markup( $args ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped as it is built. |
| 259 |
); |
| 260 |
} |
| 261 |
|
| 262 |
/** |
| 263 |
* The app's mount point, holding a plain form until the script takes over, and the hidden fields both post with. |
| 264 |
* |
| 265 |
* @param array $args Comment form arguments. |
| 266 |
* @return string |
| 267 |
*/ |
| 268 |
private function markup( $args = array() ) { |
| 269 |
$post_id = self::post_id(); |
| 270 |
$permalink = get_permalink( $post_id ); |
| 271 |
$button = sprintf( |
| 272 |
$args['submit_button'] ?? '<input name="%1$s" type="submit" id="%2$s" class="%3$s" value="%4$s" />', |
| 273 |
esc_attr( $args['name_submit'] ?? 'submit' ), |
| 274 |
esc_attr( $args['id_submit'] ?? 'submit' ), |
| 275 |
esc_attr( $args['class_submit'] ?? 'submit' ), |
| 276 |
esc_attr( $args['label_submit'] ?? _x( 'Comment', 'verb', 'jetpack-comments' ) ) |
| 277 |
); |
| 278 |
|
| 279 |
// The classes come from the button template, not class_submit: on a block |
| 280 |
// theme the Post Comments Form block bakes the theme's button classes into it. |
| 281 |
$class = preg_match( '/\bclass="([^"]*)"/', $button, $match ) ? $match[1] : 'submit'; |
| 282 |
|
| 283 |
// Values belonging to this form rather than to the page it sits on. |
| 284 |
$settings = array( |
| 285 |
'postId' => $post_id, |
| 286 |
'loginUrl' => wp_login_url( $permalink ), |
| 287 |
// wp_logout_url() runs the URL through esc_html(), which encodes single quotes too. |
| 288 |
// None on WordPress.com, where the site's session is the reader's whole WordPress.com login. |
| 289 |
'logoutUrl' => is_user_logged_in() && ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? html_entity_decode( wp_logout_url( $permalink ), ENT_QUOTES ) : '', |
| 290 |
'submit' => array( |
| 291 |
'id' => $args['id_submit'] ?? 'submit', |
| 292 |
'name' => $args['name_submit'] ?? 'submit', |
| 293 |
'class' => $class, |
| 294 |
// The block wraps its button the way the Buttons block does, so block-level button styles reach it. |
| 295 |
'wrapClass' => false !== strpos( $class, 'wp-block-button__link' ) ? 'wp-block-button' : '', |
| 296 |
'label' => $args['label_submit'] ?? _x( 'Comment', 'verb', 'jetpack-comments' ), |
| 297 |
), |
| 298 |
'subscriptions' => $args['subscriptions'] ?? array(), |
| 299 |
); |
| 300 |
|
| 301 |
// Core's own fields and submit, for a page whose settings another release rendered or whose script never ran. |
| 302 |
if ( get_option( 'comment_registration' ) && ! is_user_logged_in() ) { |
| 303 |
$plain = '<p class="must-log-in">' . sprintf( |
| 304 |
/* translators: %s is a link to the log-in page. */ |
| 305 |
esc_html__( 'You must be %s to post a comment.', 'jetpack-comments' ), |
| 306 |
'<a href="' . esc_url( wp_login_url( $permalink ) ) . '">' . esc_html__( 'logged in', 'jetpack-comments' ) . '</a>' |
| 307 |
) . '</p>'; |
| 308 |
} else { |
| 309 |
$required = (bool) get_option( 'require_name_email' ); |
| 310 |
$plain = '<p class="comment-form-comment"><label for="comment">' . esc_html_x( 'Comment', 'noun', 'jetpack-comments' ) . '</label>' |
| 311 |
. '<textarea id="comment" name="comment" rows="4" required></textarea></p>'; |
| 312 |
|
| 313 |
if ( ! is_user_logged_in() ) { |
| 314 |
$commenter = wp_get_current_commenter(); |
| 315 |
$fields = array( |
| 316 |
'author' => array( __( 'Name', 'jetpack-comments' ), 'text', $commenter['comment_author'], $required ), |
| 317 |
'email' => array( __( 'Email', 'jetpack-comments' ), 'email', $commenter['comment_author_email'], $required ), |
| 318 |
'url' => array( __( 'Website', 'jetpack-comments' ), 'url', $commenter['comment_author_url'], false ), |
| 319 |
); |
| 320 |
|
| 321 |
foreach ( $fields as $name => list( $label, $type, $value, $is_required ) ) { |
| 322 |
$plain .= sprintf( |
| 323 |
'<p class="comment-form-%1$s"><label for="%1$s">%2$s</label><input id="%1$s" name="%1$s" type="%3$s" value="%4$s"%5$s /></p>', |
| 324 |
$name, |
| 325 |
esc_html( $label ), |
| 326 |
$type, |
| 327 |
esc_attr( $value ), |
| 328 |
$is_required ? ' required' : '' |
| 329 |
); |
| 330 |
} |
| 331 |
} |
| 332 |
|
| 333 |
$plain .= sprintf( $args['submit_field'] ?? '<p class="form-submit">%1$s %2$s</p>', $button, '' ); |
| 334 |
} |
| 335 |
|
| 336 |
return '<div class="jetpack-comments"' |
| 337 |
. ' data-jetpack-comments="' . esc_attr( (string) wp_json_encode( $settings, JSON_UNESCAPED_SLASHES | JSON_HEX_AMP ) ) . '">' |
| 338 |
. $plain |
| 339 |
. '</div>' |
| 340 |
. get_comment_id_fields( $post_id ) |
| 341 |
. wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME, false, false ); |
| 342 |
} |
| 343 |
|
| 344 |
/** |
| 345 |
* The post being commented on. |
| 346 |
* |
| 347 |
* @return int |
| 348 |
*/ |
| 349 |
public static function post_id() { |
| 350 |
$post = get_post(); |
| 351 |
|
| 352 |
return $post ? $post->ID : 0; |
| 353 |
} |
| 354 |
|
| 355 |
/** |
| 356 |
* Register the bundle, and the stylesheet on a singular view. |
| 357 |
* |
| 358 |
* @return void |
| 359 |
*/ |
| 360 |
public function register_assets() { |
| 361 |
if ( wp_script_is( self::HANDLE, 'registered' ) ) { |
| 362 |
return; |
| 363 |
} |
| 364 |
|
| 365 |
// The asset version is the script's hash, so a stylesheet-only change would ship under a cached URL. |
| 366 |
$asset = include dirname( __DIR__, 2 ) . '/build/comments.asset.php'; |
| 367 |
|
| 368 |
Assets::register_script( |
| 369 |
self::HANDLE, |
| 370 |
'../../build/comments.js', |
| 371 |
__FILE__, |
| 372 |
array( |
| 373 |
'in_footer' => true, |
| 374 |
'strategy' => 'defer', |
| 375 |
'version' => $asset['version'] . '-' . (string) filemtime( dirname( __DIR__, 2 ) . '/build/comments.css' ), |
| 376 |
) |
| 377 |
); |
| 378 |
|
| 379 |
if ( is_singular() && comments_open() ) { |
| 380 |
wp_enqueue_style( self::HANDLE ); |
| 381 |
add_action( 'wp_head', array( __CLASS__, 'print_noscript_style' ) ); |
| 382 |
} |
| 383 |
} |
| 384 |
|
| 385 |
/** |
| 386 |
* Show the plain form where no script will ever replace it. |
| 387 |
* |
| 388 |
* @return void |
| 389 |
*/ |
| 390 |
public static function print_noscript_style() { |
| 391 |
echo '<noscript><style>.jetpack-comments{visibility:visible!important}</style></noscript>'; |
| 392 |
} |
| 393 |
|
| 394 |
/** |
| 395 |
* Enqueue the bundle and hand it the settings for this form. |
| 396 |
* |
| 397 |
* @param array $args Comment form arguments. |
| 398 |
* @return void |
| 399 |
*/ |
| 400 |
public function enqueue_assets( $args = array() ) { |
| 401 |
$this->register_assets(); |
| 402 |
|
| 403 |
if ( ! $this->settings_printed ) { |
| 404 |
$strings = array( |
| 405 |
'reply' => _x( 'Reply', 'verb', 'jetpack-comments' ), |
| 406 |
'commentLabel' => _x( 'Comment', 'noun', 'jetpack-comments' ), |
| 407 |
'replyLabel' => _x( 'Reply', 'noun', 'jetpack-comments' ), |
| 408 |
'placeholder' => __( 'Write a comment...', 'jetpack-comments' ), |
| 409 |
'replyPlaceholder' => __( 'Write a reply...', 'jetpack-comments' ), |
| 410 |
'name' => __( 'Name', 'jetpack-comments' ), |
| 411 |
'email' => __( 'Email', 'jetpack-comments' ), |
| 412 |
'emailHint' => __( 'Address never made public', 'jetpack-comments' ), |
| 413 |
'emailHasAccount' => __( 'That email belongs to a WordPress.com account. Log in with WordPress.com to use it, or enter a different email.', 'jetpack-comments' ), |
| 414 |
'website' => __( 'Website (optional)', 'jetpack-comments' ), |
| 415 |
'createProfile' => __( 'Create a profile', 'jetpack-comments' ), |
| 416 |
'intro' => __( 'Provide your name and email to leave a comment.', 'jetpack-comments' ), |
| 417 |
'continueAsGuest' => __( 'Continue as a guest', 'jetpack-comments' ), |
| 418 |
'postWithoutSaving' => __( 'No, thanks. I just want to post a comment', 'jetpack-comments' ), |
| 419 |
'save' => __( 'Save', 'jetpack-comments' ), |
| 420 |
'saveDetails' => __( 'Save my name, email, and website for the next time I comment.', 'jetpack-comments' ), |
| 421 |
'close' => __( 'Close', 'jetpack-comments' ), |
| 422 |
'options' => __( 'Options', 'jetpack-comments' ), |
| 423 |
'changeDetails' => __( 'Change details', 'jetpack-comments' ), |
| 424 |
'manageSubscriptions' => __( 'Manage subscription', 'jetpack-comments' ), |
| 425 |
'mustLogIn' => __( 'You must be logged in to post a comment.', 'jetpack-comments' ), |
| 426 |
'logIn' => __( 'Log in', 'jetpack-comments' ), |
| 427 |
'logInWithWordPress' => __( 'Log in with WordPress.com', 'jetpack-comments' ), |
| 428 |
'logOut' => __( 'Log out', 'jetpack-comments' ), |
| 429 |
'addYourName' => __( 'Add your name', 'jetpack-comments' ), |
| 430 |
'cancel' => __( 'Cancel', 'jetpack-comments' ), |
| 431 |
'signInFailed' => __( 'We could not sign you in. Please try again.', 'jetpack-comments' ), |
| 432 |
'signInRateLimited' => __( 'Too many sign-in attempts. Please wait a moment and try again.', 'jetpack-comments' ), |
| 433 |
); |
| 434 |
|
| 435 |
/** |
| 436 |
* Filter the copy the comment form renders. |
| 437 |
* |
| 438 |
* @since 0.1.0 |
| 439 |
* |
| 440 |
* @param array $strings Keyed by the name the app reads. |
| 441 |
* @param array $args Comment form arguments. |
| 442 |
*/ |
| 443 |
$strings = apply_filters( 'jetpack_comments_strings', $strings, $args ); |
| 444 |
$lengths = wp_get_comment_fields_max_lengths(); |
| 445 |
$style = wp_styles()->query( self::HANDLE ); |
| 446 |
|
| 447 |
// Where a reader manages subscriptions: the Reader for a WordPress.com account, the |
| 448 |
// email portal for anyone else. Only where the Newsletter offers them on this form; |
| 449 |
// Simple stores an option's "off" as an empty string, Jetpack as 0. |
| 450 |
$offered = false; |
| 451 |
foreach ( array( 'stb_enabled', 'stc_enabled' ) as $option ) { |
| 452 |
$offered = $offered || ! in_array( get_option( $option, 1 ), array( '', '0', 0 ), true ); |
| 453 |
} |
| 454 |
|
| 455 |
$reader = 'https://wordpress.com/reader/subscriptions?s=' . rawurlencode( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ); |
| 456 |
$manage = array( |
| 457 |
'url' => '', |
| 458 |
'byEmail' => true, |
| 459 |
'signedInUrl' => '', |
| 460 |
); |
| 461 |
|
| 462 |
if ( $offered && ( function_exists( 'subscription_comment_form' ) || class_exists( 'Jetpack_Subscriptions' ) ) ) { |
| 463 |
// On WordPress.com, a logged-in reader is a WordPress.com account. |
| 464 |
$by_account = defined( 'IS_WPCOM' ) && IS_WPCOM && is_user_logged_in(); |
| 465 |
$manage = array( |
| 466 |
'url' => $by_account ? $reader : 'https://subscribe.wordpress.com/', |
| 467 |
'byEmail' => ! $by_account, |
| 468 |
'signedInUrl' => $reader, |
| 469 |
); |
| 470 |
} |
| 471 |
|
| 472 |
// Everything the app needs that only PHP knows. |
| 473 |
$settings = array_merge( |
| 474 |
array( |
| 475 |
'version' => Comments::PACKAGE_VERSION, |
| 476 |
// The dialog's shadow root links it again; page styles stop at that boundary. |
| 477 |
// Decoded: WordPress.com's static-file filter joins its query with &. |
| 478 |
'styleUrl' => $style ? html_entity_decode( (string) add_query_arg( 'ver', $style->ver, $style->src ), ENT_QUOTES ) : '', |
| 479 |
'requireNameEmail' => (bool) get_option( 'require_name_email' ), |
| 480 |
'mustLogIn' => (bool) get_option( 'comment_registration' ) && ! is_user_logged_in(), |
| 481 |
'maxLength' => isset( $lengths['comment_content'] ) ? (int) $lengths['comment_content'] : 65525, |
| 482 |
'site' => array( |
| 483 |
'name' => get_bloginfo( 'name' ), |
| 484 |
'iconUrl' => (string) get_site_icon_url( 64 ), |
| 485 |
), |
| 486 |
'manageSubscriptions' => $manage, |
| 487 |
'strings' => $strings, |
| 488 |
), |
| 489 |
Identity::settings() |
| 490 |
); |
| 491 |
|
| 492 |
wp_add_inline_script( |
| 493 |
self::HANDLE, |
| 494 |
'window.JetpackComments = ' . wp_json_encode( $settings, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', |
| 495 |
'before' |
| 496 |
); |
| 497 |
$this->settings_printed = true; |
| 498 |
} |
| 499 |
|
| 500 |
Assets::enqueue_script( self::HANDLE ); |
| 501 |
wp_enqueue_style( self::HANDLE ); |
| 502 |
} |
| 503 |
|
| 504 |
/** |
| 505 |
* Require a comment to arrive with a nonce this site issued. |
| 506 |
* |
| 507 |
* For a logged-out reader it is the same string for everybody, for up to 24 |
| 508 |
* hours, so it proves the sender loaded a page from this site and nothing more. |
| 509 |
* |
| 510 |
* @param int $comment_post_id The post being commented on. |
| 511 |
* @return void |
| 512 |
*/ |
| 513 |
public function verify_nonce( $comment_post_id = 0 ) { |
| 514 |
if ( ! self::enabled_for_post_type( $comment_post_id ) ) { |
| 515 |
return; |
| 516 |
} |
| 517 |
|
| 518 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- this is the nonce check. |
| 519 |
$nonce = isset( $_POST[ self::NONCE_NAME ] ) ? sanitize_text_field( wp_unslash( $_POST[ self::NONCE_NAME ] ) ) : ''; |
| 520 |
|
| 521 |
if ( wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) { |
| 522 |
return; |
| 523 |
} |
| 524 |
|
| 525 |
// A page cache can hand a logged-in reader a copy rendered for nobody, so |
| 526 |
// the nonce they post is the anonymous one. wp_verify_nonce() reads the |
| 527 |
// session token from the logged-in cookie, not the current user, so the |
| 528 |
// cookie has to go too for the hash to match what a visitor was served. |
| 529 |
if ( defined( 'LOGGED_IN_COOKIE' ) && isset( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) { |
| 530 |
$user_id = get_current_user_id(); |
| 531 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Stashed and put back untouched. |
| 532 |
$cookie = $_COOKIE[ LOGGED_IN_COOKIE ]; |
| 533 |
|
| 534 |
unset( $_COOKIE[ LOGGED_IN_COOKIE ] ); |
| 535 |
wp_set_current_user( 0 ); |
| 536 |
|
| 537 |
$valid = (bool) wp_verify_nonce( $nonce, self::NONCE_ACTION ); |
| 538 |
|
| 539 |
$_COOKIE[ LOGGED_IN_COOKIE ] = $cookie; |
| 540 |
wp_set_current_user( $user_id ); |
| 541 |
|
| 542 |
if ( $valid ) { |
| 543 |
return; |
| 544 |
} |
| 545 |
} |
| 546 |
|
| 547 |
wp_die( |
| 548 |
esc_html__( 'Sorry, this comment could not be posted. Go back and try again.', 'jetpack-comments' ), |
| 549 |
esc_html__( 'Comment Submission Failure', 'jetpack-comments' ), |
| 550 |
array( |
| 551 |
'response' => 403, |
| 552 |
'back_link' => true, |
| 553 |
) |
| 554 |
); |
| 555 |
} |
| 556 |
} |
| 557 |
|