PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 11.2.1
Jetpack – WP Security, Backup, Speed, & Growth v11.2.1
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 / subscriptions.php

subscriptions.php in Jetpack – WP Security, Backup, Speed, & Growth 11.2.1, at modules/subscriptions.php

986 lines 30.0 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 * Module Name: Subscriptions
4 * Module Description: Let visitors subscribe to new posts and comments via email
5 * Sort Order: 9
6 * Recommendation Order: 8
7 * First Introduced: 1.2
8 * Requires Connection: Yes
9 * Requires User Connection: Yes
10 * Auto Activate: No
11 * Module Tags: Social
12 * Feature: Engagement
13 * Additional Search Queries: subscriptions, subscription, email, follow, followers, subscribers, signup
14 */
15
16 use Automattic\Jetpack\Connection\XMLRPC_Async_Call;
17
18 add_action( 'jetpack_modules_loaded', 'jetpack_subscriptions_load' );
19
20 /**
21 * Loads the Subscriptions module.
22 */
23 function jetpack_subscriptions_load() {
24 Jetpack::enable_module_configurable( __FILE__ );
25 }
26
27 /**
28 * Cherry picks keys from `$_SERVER` array.
29 *
30 * @since 6.0.0
31 *
32 * @return array An array of server data.
33 */
34 function jetpack_subscriptions_cherry_pick_server_data() {
35 $data = array();
36
37 foreach ( $_SERVER as $key => $value ) {
38 if ( ! is_string( $value ) || 0 === strpos( $key, 'HTTP_COOKIE' ) ) {
39 continue;
40 }
41
42 if ( 0 === strpos( $key, 'HTTP_' ) || in_array( $key, array( 'REMOTE_ADDR', 'REQUEST_URI', 'DOCUMENT_URI' ), true ) ) {
43 $data[ $key ] = $value;
44 }
45 }
46
47 return $data;
48 }
49
50 /**
51 * Main class file for the Subscriptions module.
52 */
53 class Jetpack_Subscriptions {
54 /**
55 * Whether Jetpack has been instantiated or not.
56 *
57 * @var bool
58 */
59 public $jetpack = false;
60
61 /**
62 * Hash of the siteurl option.
63 *
64 * @var string
65 */
66 public static $hash;
67
68 /**
69 * Singleton
70 *
71 * @static
72 */
73 public static function init() {
74 static $instance = false;
75
76 if ( ! $instance ) {
77 $instance = new Jetpack_Subscriptions();
78 }
79
80 return $instance;
81 }
82
83 /**
84 * Jetpack_Subscriptions constructor.
85 */
86 public function __construct() {
87 $this->jetpack = Jetpack::init();
88
89 // Don't use COOKIEHASH as it could be shared across installs && is non-unique in multisite.
90 // @see: https://twitter.com/nacin/status/378246957451333632 .
91 self::$hash = md5( get_option( 'siteurl' ) );
92
93 add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
94
95 // @todo remove sync from subscriptions and move elsewhere...
96
97 // Add Configuration Page.
98 add_action( 'admin_init', array( $this, 'configure' ) );
99
100 // Catch subscription widget submits.
101 if ( isset( $_REQUEST['jetpack_subscriptions_widget'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce checked in widget_submit() for logged in users.
102 add_action( 'template_redirect', array( $this, 'widget_submit' ) );
103 }
104
105 // Set up the comment subscription checkboxes.
106 add_filter( 'comment_form_submit_field', array( $this, 'comment_subscribe_init' ), 10, 2 );
107
108 // Catch comment posts and check for subscriptions.
109 add_action( 'comment_post', array( $this, 'comment_subscribe_submit' ), 50, 2 );
110
111 // Adds post meta checkbox in the post submit metabox.
112 add_action( 'post_submitbox_misc_actions', array( $this, 'subscription_post_page_metabox' ) );
113
114 add_action( 'transition_post_status', array( $this, 'maybe_send_subscription_email' ), 10, 3 );
115
116 add_filter( 'jetpack_published_post_flags', array( $this, 'set_post_flags' ), 10, 2 );
117
118 add_filter( 'post_updated_messages', array( $this, 'update_published_message' ), 18, 1 );
119
120 // Set "social_notifications_subscribe" option during the first-time activation.
121 add_action( 'jetpack_activate_module_subscriptions', array( $this, 'set_social_notifications_subscribe' ) );
122 }
123
124 /**
125 * Jetpack_Subscriptions::xmlrpc_methods()
126 *
127 * Register subscriptions methods with the Jetpack XML-RPC server.
128 *
129 * @param array $methods Methods being registered.
130 */
131 public function xmlrpc_methods( $methods ) {
132 return array_merge(
133 $methods,
134 array(
135 'jetpack.subscriptions.subscribe' => array( $this, 'subscribe' ),
136 )
137 );
138 }
139
140 /**
141 * Disable Subscribe on Single Post
142 * Register post meta
143 */
144 public function subscription_post_page_metabox() {
145 if (
146 /**
147 * Filter whether or not to show the per-post subscription option.
148 *
149 * @module subscriptions
150 *
151 * @since 3.7.0
152 *
153 * @param bool true = show checkbox option on all new posts | false = hide the option.
154 */
155 ! apply_filters( 'jetpack_allow_per_post_subscriptions', false ) ) {
156 return;
157 }
158
159 if ( has_filter( 'jetpack_subscriptions_exclude_these_categories' ) || has_filter( 'jetpack_subscriptions_include_only_these_categories' ) ) {
160 return;
161 }
162
163 global $post;
164 $disable_subscribe_value = get_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', true );
165 // only show checkbox if post hasn't been published and is a 'post' post type.
166 if ( get_post_status( $post->ID ) !== 'publish' && get_post_type( $post->ID ) === 'post' ) :
167 // Nonce it.
168 wp_nonce_field( 'disable_subscribe', 'disable_subscribe_nonce' );
169 ?>
170 <div class="misc-pub-section">
171 <label for="_jetpack_dont_email_post_to_subs"><?php esc_html_e( 'Jetpack Subscriptions:', 'jetpack' ); ?></label><br>
172 <input type="checkbox" name="_jetpack_dont_email_post_to_subs" id="jetpack-per-post-subscribe" value="1" <?php checked( $disable_subscribe_value, 1, true ); ?> />
173 <?php esc_html_e( 'Don&#8217;t send this to subscribers', 'jetpack' ); ?>
174 </div>
175 <?php
176 endif;
177 }
178
179 /**
180 * Checks whether or not the post should be emailed to subscribers
181 *
182 * It checks for the following things in order:
183 * - Usage of filter jetpack_subscriptions_exclude_these_categories
184 * - Usage of filter jetpack_subscriptions_include_only_these_categories
185 * - Existence of the per-post checkbox option
186 *
187 * Only one of these can be used at any given time.
188 *
189 * @param string $new_status Tthe "new" post status of the transition when saved.
190 * @param string $old_status The "old" post status of the transition when saved.
191 * @param object $post obj The post object.
192 */
193 public function maybe_send_subscription_email( $new_status, $old_status, $post ) {
194
195 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
196 return;
197 }
198
199 // Make sure that the checkbox is preseved.
200 if ( ! empty( $_POST['disable_subscribe_nonce'] ) && wp_verify_nonce( $_POST['disable_subscribe_nonce'], 'disable_subscribe' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP Core doesn't unslash or sanitize nonces either.
201 $set_checkbox = isset( $_POST['_jetpack_dont_email_post_to_subs'] ) ? 1 : 0;
202 update_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', $set_checkbox );
203 }
204 }
205
206 /**
207 * Message used when publishing a post.
208 *
209 * @param array $messages Message array for a post.
210 */
211 public function update_published_message( $messages ) {
212 global $post;
213 if ( ! $this->should_email_post_to_subscribers( $post ) ) {
214 return $messages;
215 }
216
217 $view_post_link_html = sprintf(
218 ' <a href="%1$s">%2$s</a>',
219 esc_url( get_permalink( $post ) ),
220 __( 'View post', 'jetpack' )
221 );
222
223 $messages['post'][6] = sprintf(
224 /* translators: Message shown after a post is published */
225 esc_html__( 'Post published and sending emails to subscribers.', 'jetpack' )
226 ) . $view_post_link_html;
227 return $messages;
228 }
229
230 /**
231 * Determine if a post should notifiy subscribers via email.
232 *
233 * @param object $post The post.
234 */
235 public function should_email_post_to_subscribers( $post ) {
236 $should_email = true;
237 if ( get_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', true ) ) {
238 return false;
239 }
240
241 // Only posts are currently supported.
242 if ( 'post' !== $post->post_type ) {
243 return false;
244 }
245
246 // Private posts are not sent to subscribers.
247 if ( 'private' === $post->post_status ) {
248 return false;
249 }
250
251 /**
252 * Array of categories that will never trigger subscription emails.
253 *
254 * Will not send subscription emails from any post from within these categories.
255 *
256 * @module subscriptions
257 *
258 * @since 3.7.0
259 *
260 * @param array $args Array of category slugs or ID's.
261 */
262 $excluded_categories = apply_filters( 'jetpack_subscriptions_exclude_these_categories', array() );
263
264 // Never email posts from these categories.
265 if ( ! empty( $excluded_categories ) && in_category( $excluded_categories, $post->ID ) ) {
266 $should_email = false;
267 }
268
269 /**
270 * ONLY send subscription emails for these categories
271 *
272 * Will ONLY send subscription emails to these categories.
273 *
274 * @module subscriptions
275 *
276 * @since 3.7.0
277 *
278 * @param array $args Array of category slugs or ID's.
279 */
280 $only_these_categories = apply_filters( 'jetpack_subscriptions_exclude_all_categories_except', array() );
281
282 // Only emails posts from these categories.
283 if ( ! empty( $only_these_categories ) && ! in_category( $only_these_categories, $post->ID ) ) {
284 $should_email = false;
285 }
286
287 return $should_email;
288 }
289
290 /**
291 * Retrieve which flags should be added to a particular post.
292 *
293 * @param array $flags Flags to be added.
294 * @param object $post A post object.
295 */
296 public function set_post_flags( $flags, $post ) {
297 $flags['send_subscription'] = $this->should_email_post_to_subscribers( $post );
298 return $flags;
299 }
300
301 /**
302 * Jetpack_Subscriptions::configure()
303 *
304 * Jetpack Subscriptions configuration screen.
305 */
306 public function configure() {
307 // Create the section.
308 add_settings_section(
309 'jetpack_subscriptions',
310 __( 'Jetpack Subscriptions Settings', 'jetpack' ),
311 array( $this, 'subscriptions_settings_section' ),
312 'discussion'
313 );
314
315 /** Subscribe to Posts */
316
317 add_settings_field(
318 'jetpack_subscriptions_post_subscribe',
319 __( 'Follow Blog', 'jetpack' ),
320 array( $this, 'subscription_post_subscribe_setting' ),
321 'discussion',
322 'jetpack_subscriptions'
323 );
324
325 register_setting(
326 'discussion',
327 'stb_enabled'
328 );
329
330 /** Subscribe to Comments */
331
332 add_settings_field(
333 'jetpack_subscriptions_comment_subscribe',
334 __( 'Follow Comments', 'jetpack' ),
335 array( $this, 'subscription_comment_subscribe_setting' ),
336 'discussion',
337 'jetpack_subscriptions'
338 );
339
340 register_setting(
341 'discussion',
342 'stc_enabled'
343 );
344
345 /** Email me whenever: Someone follows my blog */
346 /* @since 8.1 */
347
348 add_settings_section(
349 'notifications_section',
350 __( 'Someone follows my blog', 'jetpack' ),
351 array( $this, 'social_notifications_subscribe_section' ),
352 'discussion'
353 );
354
355 add_settings_field(
356 'jetpack_subscriptions_social_notifications_subscribe',
357 __( 'Email me whenever', 'jetpack' ),
358 array( $this, 'social_notifications_subscribe_field' ),
359 'discussion',
360 'notifications_section'
361 );
362
363 register_setting(
364 'discussion',
365 'social_notifications_subscribe',
366 array( $this, 'social_notifications_subscribe_validate' )
367 );
368
369 /** Subscription Messaging Options */
370
371 register_setting(
372 'reading',
373 'subscription_options',
374 array( $this, 'validate_settings' )
375 );
376
377 add_settings_section(
378 'email_settings',
379 __( 'Follower Settings', 'jetpack' ),
380 array( $this, 'reading_section' ),
381 'reading'
382 );
383
384 add_settings_field(
385 'invitation',
386 __( 'Blog follow email text', 'jetpack' ),
387 array( $this, 'setting_invitation' ),
388 'reading',
389 'email_settings'
390 );
391
392 add_settings_field(
393 'comment-follow',
394 __( 'Comment follow email text', 'jetpack' ),
395 array( $this, 'setting_comment_follow' ),
396 'reading',
397 'email_settings'
398 );
399 }
400
401 /**
402 * Discussions setting section blurb.
403 */
404 public function subscriptions_settings_section() {
405 ?>
406 <p id="jetpack-subscriptions-settings"><?php esc_html_e( 'Change whether your visitors can subscribe to your posts or comments or both.', 'jetpack' ); ?></p>
407
408 <?php
409 }
410
411 /**
412 * Post Subscriptions Toggle.
413 */
414 public function subscription_post_subscribe_setting() {
415
416 $stb_enabled = get_option( 'stb_enabled', 1 );
417 ?>
418
419 <p class="description">
420 <input type="checkbox" name="stb_enabled" id="jetpack-post-subscribe" value="1" <?php checked( $stb_enabled, 1 ); ?> />
421 <?php
422 echo wp_kses(
423 __(
424 "Show a <em>'follow blog'</em> option in the comment form",
425 'jetpack'
426 ),
427 array( 'em' => array() )
428 );
429 ?>
430 </p>
431 <?php
432 }
433
434 /**
435 * Comments Subscriptions Toggle.
436 */
437 public function subscription_comment_subscribe_setting() {
438
439 $stc_enabled = get_option( 'stc_enabled', 1 );
440 ?>
441
442 <p class="description">
443 <input type="checkbox" name="stc_enabled" id="jetpack-comment-subscribe" value="1" <?php checked( $stc_enabled, 1 ); ?> />
444 <?php
445 echo wp_kses(
446 __(
447 "Show a <em>'follow comments'</em> option in the comment form",
448 'jetpack'
449 ),
450 array( 'em' => array() )
451 );
452 ?>
453 </p>
454
455 <?php
456 }
457
458 /**
459 * Someone follows my blog section
460 *
461 * @since 8.1
462 */
463 public function social_notifications_subscribe_section() {
464 // Atypical usage here. We emit jquery to move subscribe notification checkbox to be with the rest of the email notification settings.
465 ?>
466 <script type="text/javascript">
467 jQuery( function( $ ) {
468 var table = $( '#social_notifications_subscribe' ).parents( 'table:first' ),
469 header = table.prevAll( 'h2:first' ),
470 newParent = $( '#moderation_notify' ).parent( 'label' ).parent();
471
472 if ( ! table.length || ! header.length || ! newParent.length ) {
473 return;
474 }
475
476 newParent.append( '<br/>' ).append( table.end().parent( 'label' ).siblings().andSelf() );
477 header.remove();
478 table.remove();
479 } );
480 </script>
481 <?php
482 }
483
484 /**
485 * Someone follows my blog Toggle
486 *
487 * @since 8.1
488 */
489 public function social_notifications_subscribe_field() {
490 $checked = (int) ( 'on' === get_option( 'social_notifications_subscribe', 'on' ) );
491 ?>
492
493 <label>
494 <input type="checkbox" name="social_notifications_subscribe" id="social_notifications_subscribe" value="1" <?php checked( $checked ); ?> />
495 <?php
496 /* translators: this is a label for a setting that starts with "Email me whenever" */
497 esc_html_e( 'Someone follows my blog', 'jetpack' );
498 ?>
499 </label>
500 <?php
501 }
502
503 /**
504 * Validate "Someone follows my blog" option
505 *
506 * @since 8.1
507 *
508 * @param String $input the input string to be validated.
509 * @return string on|off
510 */
511 public function social_notifications_subscribe_validate( $input ) {
512 // If it's not set (was unchecked during form submission) or was set to off (during option update), return 'off'.
513 if ( ! $input || 'off' === $input ) {
514 return 'off';
515 }
516
517 // Otherwise we return 'on'.
518 return 'on';
519 }
520
521 /**
522 * Validate settings for the Subscriptions module.
523 *
524 * @param array $settings Settings to be validated.
525 */
526 public function validate_settings( $settings ) {
527 global $allowedposttags;
528
529 $default = $this->get_default_settings();
530
531 // Blog Follow.
532 $settings['invitation'] = trim( wp_kses( $settings['invitation'], $allowedposttags ) );
533 if ( empty( $settings['invitation'] ) ) {
534 $settings['invitation'] = $default['invitation'];
535 }
536
537 // Comments Follow (single post).
538 $settings['comment_follow'] = trim( wp_kses( $settings['comment_follow'], $allowedposttags ) );
539 if ( empty( $settings['comment_follow'] ) ) {
540 $settings['comment_follow'] = $default['comment_follow'];
541 }
542
543 return $settings;
544 }
545
546 /**
547 * HTML output helper for Reading section.
548 */
549 public function reading_section() {
550 echo '<p id="follower-settings">';
551 esc_html_e( 'These settings change emails sent from your blog to followers.', 'jetpack' );
552 echo '</p>';
553 }
554
555 /**
556 * HTML output helper for Invitation section.
557 */
558 public function setting_invitation() {
559 $settings = $this->get_settings();
560 echo '<textarea name="subscription_options[invitation]" class="large-text" cols="50" rows="5">' . esc_textarea( $settings['invitation'] ) . '</textarea>';
561 echo '<p><span class="description">' . esc_html__( 'Introduction text sent when someone follows your blog. (Site and confirmation details will be automatically added for you.)', 'jetpack' ) . '</span></p>';
562 }
563
564 /**
565 * HTML output helper for Comment Follow section.
566 */
567 public function setting_comment_follow() {
568 $settings = $this->get_settings();
569 echo '<textarea name="subscription_options[comment_follow]" class="large-text" cols="50" rows="5">' . esc_textarea( $settings['comment_follow'] ) . '</textarea>';
570 echo '<p><span class="description">' . esc_html__( 'Introduction text sent when someone follows a post on your blog. (Site and confirmation details will be automatically added for you.)', 'jetpack' ) . '</span></p>';
571 }
572
573 /**
574 * Get default settings for the Subscriptions module.
575 */
576 public function get_default_settings() {
577 $site_url = get_home_url();
578 $display_url = preg_replace( '(^https?://)', '', untrailingslashit( $site_url ) );
579
580 return array(
581 /* translators: Both %1$s and %2$s is site address */
582 'invitation' => sprintf( __( "Howdy,\nYou recently subscribed to <a href='%1\$s'>%2\$s</a> and we need to verify the email you provided. Once you confirm below, you'll be able to receive and read new posts.\n\nIf you believe this is an error, ignore this message and nothing more will happen.", 'jetpack' ), $site_url, $display_url ),
583 'comment_follow' => __( "Howdy.\n\nYou recently followed one of my posts. This means you will receive an email when new comments are posted.\n\nTo activate, click confirm below. If you believe this is an error, ignore this message and we'll never bother you again.", 'jetpack' ),
584 );
585 }
586
587 /**
588 * Reeturn merged `subscription_options` option with module default settings.
589 */
590 public function get_settings() {
591 return wp_parse_args( (array) get_option( 'subscription_options', array() ), $this->get_default_settings() );
592 }
593
594 /**
595 * Jetpack_Subscriptions::subscribe()
596 *
597 * Send a synchronous XML-RPC subscribe to blog posts or subscribe to post comments request.
598 *
599 * @param string $email being subscribed.
600 * @param array $post_ids (optional) defaults to 0 for blog posts only: array of post IDs to subscribe to blog's posts.
601 * @param bool $async (optional) Should the subscription be performed asynchronously? Defaults to true.
602 * @param array $extra_data Additional data passed to the `jetpack.subscribeToSite` call.
603 *
604 * @return true|WP_Error true on success
605 * invalid_email : not a valid email address
606 * invalid_post_id : not a valid post ID
607 * unknown_post_id : unknown post
608 * not_subscribed : strange error. Jetpack servers at WordPress.com could subscribe the email.
609 * disabled : Site owner has disabled subscriptions.
610 * active : Already subscribed.
611 * pending : Tried to subscribe before but the confirmation link is never clicked. No confirmation email is sent.
612 * unknown : strange error. Jetpack servers at WordPress.com returned something malformed.
613 * unknown_status : strange error. Jetpack servers at WordPress.com returned something I didn't understand.
614 */
615 public function subscribe( $email, $post_ids = 0, $async = true, $extra_data = array() ) {
616 if ( ! is_email( $email ) ) {
617 return new WP_Error( 'invalid_email' );
618 }
619
620 if ( ! $async ) {
621 $xml = new Jetpack_IXR_ClientMulticall();
622 }
623
624 foreach ( (array) $post_ids as $post_id ) {
625 $post_id = (int) $post_id;
626 if ( $post_id < 0 ) {
627 return new WP_Error( 'invalid_post_id' );
628 } elseif ( $post_id && ! get_post( $post_id ) ) {
629 return new WP_Error( 'unknown_post_id' );
630 }
631
632 if ( $async ) {
633 XMLRPC_Async_Call::add_call( 'jetpack.subscribeToSite', 0, $email, $post_id, serialize( $extra_data ) ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
634 } else {
635 $xml->addCall( 'jetpack.subscribeToSite', $email, $post_id, serialize( $extra_data ) ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
636 }
637 }
638
639 if ( $async ) {
640 return;
641 }
642
643 // Call.
644 $xml->query();
645
646 if ( $xml->isError() ) {
647 return $xml->get_jetpack_error();
648 }
649
650 $responses = $xml->getResponse();
651
652 $r = array();
653 foreach ( (array) $responses as $response ) {
654 if ( isset( $response['faultCode'] ) || isset( $response['faultString'] ) ) {
655 $r[] = $xml->get_jetpack_error( $response['faultCode'], $response['faultString'] );
656 continue;
657 }
658
659 if ( ! is_array( $response[0] ) || empty( $response[0]['status'] ) ) {
660 $r[] = new WP_Error( 'unknown' );
661 continue;
662 }
663
664 switch ( $response[0]['status'] ) {
665 case 'error':
666 $r[] = new WP_Error( 'not_subscribed' );
667 continue 2;
668 case 'disabled':
669 $r[] = new WP_Error( 'disabled' );
670 continue 2;
671 case 'active':
672 $r[] = new WP_Error( 'active' );
673 continue 2;
674 case 'confirming':
675 $r[] = true;
676 continue 2;
677 case 'pending':
678 $r[] = new WP_Error( 'pending' );
679 continue 2;
680 default:
681 $r[] = new WP_Error( 'unknown_status', (string) $response[0]['status'] );
682 continue 2;
683 }
684 }
685
686 return $r;
687 }
688
689 /**
690 * Jetpack_Subscriptions::widget_submit()
691 *
692 * When a user submits their email via the blog subscription widget, check the details and call the subsribe() method.
693 */
694 public function widget_submit() {
695 // Check the nonce.
696 if ( is_user_logged_in() ) {
697 check_admin_referer( 'blogsub_subscribe_' . get_current_blog_id() );
698 }
699
700 if ( empty( $_REQUEST['email'] ) || ! is_string( $_REQUEST['email'] ) ) {
701 return false;
702 }
703
704 $redirect_fragment = false;
705 if ( isset( $_REQUEST['redirect_fragment'] ) ) {
706 $redirect_fragment = preg_replace( '/[^a-z0-9_-]/i', '', $_REQUEST['redirect_fragment'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is manually unslashing and sanitizing.
707 }
708 if ( ! $redirect_fragment || ! is_string( $redirect_fragment ) ) {
709 $redirect_fragment = 'subscribe-blog';
710 }
711
712 $subscribe = self::subscribe(
713 isset( $_REQUEST['email'] ) ? wp_unslash( $_REQUEST['email'] ) : null, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated inside self::subscribe().
714 0,
715 false,
716 array(
717 'source' => 'widget',
718 'widget-in-use' => is_active_widget( false, false, 'blog_subscription', true ) ? 'yes' : 'no',
719 'comment_status' => '',
720 'server_data' => jetpack_subscriptions_cherry_pick_server_data(),
721 )
722 );
723
724 if ( is_wp_error( $subscribe ) ) {
725 $error = $subscribe->get_error_code();
726 } else {
727 $error = false;
728 foreach ( $subscribe as $response ) {
729 if ( is_wp_error( $response ) ) {
730 $error = $response->get_error_code();
731 break;
732 }
733 }
734 }
735
736 switch ( $error ) {
737 case false:
738 $result = 'success';
739 break;
740 case 'invalid_email':
741 $result = $error;
742 break;
743 case 'blocked_email':
744 $result = 'opted_out';
745 break;
746 case 'active':
747 $result = 'already';
748 break;
749 case 'flooded_email':
750 $result = 'many_pending_subs';
751 break;
752 case 'pending':
753 $result = 'pending';
754 break;
755 default:
756 $result = 'error';
757 break;
758 }
759
760 $redirect = add_query_arg( 'subscribe', $result );
761
762 /**
763 * Fires on each subscription form submission.
764 *
765 * @module subscriptions
766 *
767 * @since 3.7.0
768 *
769 * @param string $result Result of form submission: success, invalid_email, already, error.
770 */
771 do_action( 'jetpack_subscriptions_form_submission', $result );
772
773 wp_safe_redirect( "$redirect#$redirect_fragment" );
774 exit;
775 }
776
777 /**
778 * Jetpack_Subscriptions::comment_subscribe_init()
779 *
780 * Set up and add the comment subscription checkbox to the comment form.
781 *
782 * @param string $submit_button HTML markup for the submit field.
783 */
784 public function comment_subscribe_init( $submit_button ) {
785 global $post;
786
787 $comments_checked = '';
788 $blog_checked = '';
789
790 // Check for a comment / blog submission and set a cookie to retain the setting and check the boxes.
791 if ( isset( $_COOKIE[ 'jetpack_comments_subscribe_' . self::$hash . '_' . $post->ID ] ) ) {
792 $comments_checked = ' checked="checked"';
793 }
794
795 if ( isset( $_COOKIE[ 'jetpack_blog_subscribe_' . self::$hash ] ) ) {
796 $blog_checked = ' checked="checked"';
797 }
798
799 // Some themes call this function, don't show the checkbox again.
800 remove_action( 'comment_form', 'subscription_comment_form' );
801
802 // Check if Mark Jaquith's Subscribe to Comments plugin is active - if so, suppress Jetpack checkbox.
803
804 $str = '';
805
806 if ( false === has_filter( 'comment_form', 'show_subscription_checkbox' ) && 1 === (int) get_option( 'stc_enabled', 1 ) && empty( $post->post_password ) && 'post' === get_post_type() ) {
807 // Subscribe to comments checkbox.
808 $str .= '<p class="comment-subscription-form"><input type="checkbox" name="subscribe_comments" id="subscribe_comments" value="subscribe" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"' . $comments_checked . ' /> ';
809 $comment_sub_text = __( 'Notify me of follow-up comments by email.', 'jetpack' );
810 $str .= '<label class="subscribe-label" id="subscribe-label" for="subscribe_comments">' . esc_html(
811 /**
812 * Filter the Subscribe to comments text appearing below the comment form.
813 *
814 * @module subscriptions
815 *
816 * @since 3.4.0
817 *
818 * @param string $comment_sub_text Subscribe to comments text.
819 */
820 apply_filters( 'jetpack_subscribe_comment_label', $comment_sub_text )
821 ) . '</label>';
822 $str .= '</p>';
823 }
824
825 if ( 1 === (int) get_option( 'stb_enabled', 1 ) ) {
826 // Subscribe to blog checkbox.
827 $str .= '<p class="comment-subscription-form"><input type="checkbox" name="subscribe_blog" id="subscribe_blog" value="subscribe" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"' . $blog_checked . ' /> ';
828 $blog_sub_text = __( 'Notify me of new posts by email.', 'jetpack' );
829 $str .= '<label class="subscribe-label" id="subscribe-blog-label" for="subscribe_blog">' . esc_html(
830 /**
831 * Filter the Subscribe to blog text appearing below the comment form.
832 *
833 * @module subscriptions
834 *
835 * @since 3.4.0
836 *
837 * @param string $comment_sub_text Subscribe to blog text.
838 */
839 apply_filters( 'jetpack_subscribe_blog_label', $blog_sub_text )
840 ) . '</label>';
841 $str .= '</p>';
842 }
843
844 /**
845 * Filter the output of the subscription options appearing below the comment form.
846 *
847 * @module subscriptions
848 *
849 * @since 1.2.0
850 *
851 * @param string $str Comment Subscription form HTML output.
852 */
853 $str = apply_filters( 'jetpack_comment_subscription_form', $str );
854
855 return $str . $submit_button;
856 }
857
858 /**
859 * Jetpack_Subscriptions::comment_subscribe_init()
860 *
861 * When a user checks the comment subscribe box and submits a comment, subscribe them to the comment thread.
862 *
863 * @param int|string $comment_id Comment thread being subscribed to.
864 * @param string $approved Comment status.
865 */
866 public function comment_subscribe_submit( $comment_id, $approved ) {
867 if ( 'spam' === $approved ) {
868 return;
869 }
870
871 $comment = get_comment( $comment_id );
872 if ( ! $comment ) {
873 return;
874 }
875
876 // Set cookies for this post/comment.
877 $this->set_cookies( isset( $_REQUEST['subscribe_comments'] ), $comment->comment_post_ID, isset( $_REQUEST['subscribe_blog'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
878
879 if ( ! isset( $_REQUEST['subscribe_comments'] ) && ! isset( $_REQUEST['subscribe_blog'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
880 return;
881 }
882
883 $post_ids = array();
884
885 if ( isset( $_REQUEST['subscribe_comments'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
886 $post_ids[] = $comment->comment_post_ID;
887 }
888
889 if ( isset( $_REQUEST['subscribe_blog'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
890 $post_ids[] = 0;
891 }
892
893 $result = self::subscribe(
894 $comment->comment_author_email,
895 $post_ids,
896 true,
897 array(
898 'source' => 'comment-form',
899 'widget-in-use' => is_active_widget( false, false, 'blog_subscription', true ) ? 'yes' : 'no',
900 'comment_status' => $approved,
901 'server_data' => jetpack_subscriptions_cherry_pick_server_data(),
902 )
903 );
904
905 /**
906 * Fires on each comment subscription form submission.
907 *
908 * @module subscriptions
909 *
910 * @since 5.5.0
911 *
912 * @param NULL|WP_Error $result Result of form submission: NULL on success, WP_Error otherwise.
913 * @param array $post_ids An array of post IDs that the user subscribed to, 0 means blog subscription.
914 */
915 do_action( 'jetpack_subscriptions_comment_form_submission', $result, $post_ids );
916 }
917
918 /**
919 * Jetpack_Subscriptions::set_cookies()
920 *
921 * Set a cookie to save state on the comment and post subscription checkboxes.
922 *
923 * @param bool $subscribe_to_post Whether the user chose to subscribe to subsequent comments on this post.
924 * @param int $post_id If $subscribe_to_post is true, the post ID they've subscribed to.
925 * @param bool $subscribe_to_blog Whether the user chose to subscribe to all new posts on the blog.
926 */
927 public function set_cookies( $subscribe_to_post = false, $post_id = null, $subscribe_to_blog = false ) {
928 $post_id = (int) $post_id;
929
930 /** This filter is already documented in core/wp-includes/comment-functions.php */
931 $cookie_lifetime = apply_filters( 'comment_cookie_lifetime', 30000000 );
932
933 /**
934 * Filter the Jetpack Comment cookie path.
935 *
936 * @module subscriptions
937 *
938 * @since 2.5.0
939 *
940 * @param string COOKIEPATH Cookie path.
941 */
942 $cookie_path = apply_filters( 'jetpack_comment_cookie_path', COOKIEPATH );
943
944 /**
945 * Filter the Jetpack Comment cookie domain.
946 *
947 * @module subscriptions
948 *
949 * @since 2.5.0
950 *
951 * @param string COOKIE_DOMAIN Cookie domain.
952 */
953 $cookie_domain = apply_filters( 'jetpack_comment_cookie_domain', COOKIE_DOMAIN );
954
955 if ( $subscribe_to_post && $post_id >= 0 ) {
956 setcookie( 'jetpack_comments_subscribe_' . self::$hash . '_' . $post_id, 1, time() + $cookie_lifetime, $cookie_path, $cookie_domain, is_ssl(), true );
957 } else {
958 setcookie( 'jetpack_comments_subscribe_' . self::$hash . '_' . $post_id, '', time() - 3600, $cookie_path, $cookie_domain, is_ssl(), true );
959 }
960
961 if ( $subscribe_to_blog ) {
962 setcookie( 'jetpack_blog_subscribe_' . self::$hash, 1, time() + $cookie_lifetime, $cookie_path, $cookie_domain, is_ssl(), true );
963 } else {
964 setcookie( 'jetpack_blog_subscribe_' . self::$hash, '', time() - 3600, $cookie_path, $cookie_domain, is_ssl(), true );
965 }
966 }
967
968 /**
969 * Set the social_notifications_subscribe option to `off` when the Subscriptions module is activated in the first time.
970 *
971 * @since 8.1
972 *
973 * @return void
974 */
975 public function set_social_notifications_subscribe() {
976 if ( false === get_option( 'social_notifications_subscribe' ) ) {
977 add_option( 'social_notifications_subscribe', 'off' );
978 }
979 }
980
981 }
982
983 Jetpack_Subscriptions::init();
984
985 require __DIR__ . '/subscriptions/views.php';
986