PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 11.9.1
Jetpack – WP Security, Backup, Speed, & Growth v11.9.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.9.1, at modules/subscriptions.php

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