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