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

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

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