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