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