PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 14.4
Jetpack – WP Security, Backup, Speed, & Growth v14.4
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 14.4, at modules/subscriptions.php

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