Completed
Push — fix/package-release-script ( 8004a7...315d04 )
by
unknown
06:57
created

Jetpack_Subscriptions   F

Complexity

Total Complexity 101

Size/Duplication

Total Lines 845
Duplicated Lines 2.49 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 101
lcom 2
cbo 3
dl 21
loc 845
rs 1.755
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 9 2
A __construct() 0 36 2
A xmlrpc_methods() 0 8 1
B subscription_post_page_metabox() 0 34 6
B comment_subscribe_init() 0 73 8
B comment_subscribe_submit() 0 45 7
A set_cookies() 0 40 4
A maybe_send_subscription_email() 0 12 6
A update_published_message() 0 17 2
B should_email_post_to_subscribers() 0 54 8
A set_post_flags() 0 4 1
B configure() 0 94 1
A subscriptions_settings_section() 0 6 1
A subscription_post_subscribe_setting() 10 10 1
A subscription_comment_subscribe_setting() 11 11 1
A social_notifications_subscribe_section() 0 20 1
A social_notifications_subscribe_field() 0 13 1
A social_notifications_subscribe_validate() 0 9 3
A validate_settings() 0 17 3
A reading_section() 0 5 1
A setting_invitation() 0 5 1
A setting_comment_follow() 0 5 1
A get_default_settings() 0 6 1
A get_settings() 0 3 1
D subscribe() 0 73 20
D widget_submit() 0 81 15
A set_social_notifications_subscribe() 0 5 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Subscriptions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Subscriptions, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Module Name: Subscriptions
4
 * Module Description: Let visitors subscribe to new posts and comments via email
5
 * Sort Order: 9
6
 * Recommendation Order: 8
7
 * First Introduced: 1.2
8
 * Requires Connection: Yes
9
 * Auto Activate: No
10
 * Module Tags: Social
11
 * Feature: Engagement
12
 * Additional Search Queries: subscriptions, subscription, email, follow, followers, subscribers, signup
13
 */
14
15
add_action( 'jetpack_modules_loaded', 'jetpack_subscriptions_load' );
16
17
function jetpack_subscriptions_load() {
18
	Jetpack::enable_module_configurable( __FILE__ );
19
}
20
21
/**
22
 * Cherry picks keys from `$_SERVER` array.
23
 *
24
 * @since 6.0.0
25
 *
26
 * @return array An array of server data.
27
 */
28
function jetpack_subscriptions_cherry_pick_server_data() {
29
	$data = array();
30
31
	foreach ( $_SERVER as $key => $value ) {
32
		if ( ! is_string( $value ) || 0 === strpos( $key, 'HTTP_COOKIE' ) ) {
33
			continue;
34
		}
35
36
		if ( 0 === strpos( $key, 'HTTP_' ) || in_array( $key, array( 'REMOTE_ADDR', 'REQUEST_URI', 'DOCUMENT_URI' ), true ) ) {
37
			$data[ $key ] = $value;
38
		}
39
	}
40
41
	return $data;
42
}
43
44
class Jetpack_Subscriptions {
45
	public $jetpack = false;
46
47
	public static $hash;
48
49
	/**
50
	 * Singleton
51
	 * @static
52
	 */
53
	static function init() {
54
		static $instance = false;
55
56
		if ( !$instance ) {
57
			$instance = new Jetpack_Subscriptions;
58
		}
59
60
		return $instance;
61
	}
62
63
	function __construct() {
64
		$this->jetpack = Jetpack::init();
0 ignored issues
show
Documentation Bug introduced by
It seems like \Jetpack::init() of type object<Jetpack> is incompatible with the declared type boolean of property $jetpack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
65
66
		// Don't use COOKIEHASH as it could be shared across installs && is non-unique in multisite.
67
		// @see: https://twitter.com/nacin/status/378246957451333632
68
		self::$hash = md5( get_option( 'siteurl' ) );
69
70
		add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
71
72
		// @todo remove sync from subscriptions and move elsewhere...
73
74
		// Add Configuration Page
75
		add_action( 'admin_init', array( $this, 'configure' ) );
76
77
		// Catch subscription widget submits
78
		if ( isset( $_REQUEST['jetpack_subscriptions_widget'] ) )
79
			add_action( 'template_redirect', array( $this, 'widget_submit' ) );
80
81
		// Set up the comment subscription checkboxes
82
		add_filter( 'comment_form_submit_field', array( $this, 'comment_subscribe_init' ), 10, 2 );
83
84
		// Catch comment posts and check for subscriptions.
85
		add_action( 'comment_post', array( $this, 'comment_subscribe_submit' ), 50, 2 );
86
87
		// Adds post meta checkbox in the post submit metabox
88
		add_action( 'post_submitbox_misc_actions', array( $this, 'subscription_post_page_metabox' ) );
89
90
		add_action( 'transition_post_status', array( $this, 'maybe_send_subscription_email' ), 10, 3 );
91
92
		add_filter( 'jetpack_published_post_flags', array( $this, 'set_post_flags' ), 10, 2 );
93
94
		add_filter( 'post_updated_messages', array( $this, 'update_published_message' ), 18, 1 );
95
96
		// Set "social_notifications_subscribe" option during the first-time activation.
97
		add_action( 'jetpack_activate_module_subscriptions',   array( $this, 'set_social_notifications_subscribe' ) );
98
	}
99
100
	/**
101
	 * Jetpack_Subscriptions::xmlrpc_methods()
102
	 *
103
	 * Register subscriptions methods with the Jetpack XML-RPC server.
104
	 * @param array $methods
105
	 */
106
	function xmlrpc_methods( $methods ) {
107
		return array_merge(
108
			$methods,
109
			array(
110
				'jetpack.subscriptions.subscribe' => array( $this, 'subscribe' ),
111
			)
112
		);
113
	}
114
115
	/*
116
	 * Disable Subscribe on Single Post
117
	 * Register post meta
118
	 */
119
	function subscription_post_page_metabox() {
120
		if (
121
			/**
122
			 * Filter whether or not to show the per-post subscription option.
123
			 *
124
			 * @module subscriptions
125
			 *
126
			 * @since 3.7.0
127
			 *
128
			 * @param bool true = show checkbox option on all new posts | false = hide the option.
129
			 */
130
			 ! apply_filters( 'jetpack_allow_per_post_subscriptions', false ) )
131
		{
132
			return;
133
		}
134
135
		if ( has_filter( 'jetpack_subscriptions_exclude_these_categories' ) || has_filter( 'jetpack_subscriptions_include_only_these_categories' ) ) {
136
			return;
137
		}
138
139
		global $post;
140
		$disable_subscribe_value = get_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', true );
141
		// only show checkbox if post hasn't been published and is a 'post' post type.
142
		if ( get_post_status( $post->ID ) !== 'publish' && get_post_type( $post->ID ) == 'post' ) :
143
			// Nonce it
144
			wp_nonce_field( 'disable_subscribe', 'disable_subscribe_nonce' );
145
			?>
146
			<div class="misc-pub-section">
147
				<label for="_jetpack_dont_email_post_to_subs"><?php _e( 'Jetpack Subscriptions:', 'jetpack' ); ?></label><br>
148
				<input type="checkbox" name="_jetpack_dont_email_post_to_subs" id="jetpack-per-post-subscribe" value="1" <?php checked( $disable_subscribe_value, 1, true ); ?> />
149
				<?php _e( 'Don&#8217;t send this to subscribers', 'jetpack' ); ?>
150
			</div>
151
		<?php endif;
152
	}
153
154
	/**
155
	 * Checks whether or not the post should be emailed to subscribers
156
	 *
157
	 * It checks for the following things in order:
158
	 * - Usage of filter jetpack_subscriptions_exclude_these_categories
159
	 * - Usage of filter jetpack_subscriptions_include_only_these_categories
160
	 * - Existence of the per-post checkbox option
161
	 *
162
	 * Only one of these can be used at any given time.
163
	 *
164
	 * @param $new_status string - the "new" post status of the transition when saved
165
	 * @param $old_status string - the "old" post status of the transition when saved
166
	 * @param $post obj - The post object
167
	 */
168
	function maybe_send_subscription_email( $new_status, $old_status, $post ) {
169
170
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
171
			return;
172
		}
173
174
		// Make sure that the checkbox is preseved
175
		if ( ! empty( $_POST['disable_subscribe_nonce'] ) && wp_verify_nonce( $_POST['disable_subscribe_nonce'], 'disable_subscribe' ) ) {
176
			$set_checkbox = isset( $_POST['_jetpack_dont_email_post_to_subs'] ) ? 1 : 0;
177
			update_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', $set_checkbox );
178
		}
179
	}
180
181
	function update_published_message( $messages ) {
182
		global $post;
183
		if ( ! $this->should_email_post_to_subscribers( $post ) ) {
184
			return $messages;
185
		}
186
187
		$view_post_link_html = sprintf( ' <a href="%1$s">%2$s</a>',
188
			esc_url( get_permalink( $post ) ),
189
			__( 'View post', 'jetpack' )
190
		);
191
192
		$messages['post'][6] = sprintf(
193
			/* translators: Message shown after a post is published */
194
			esc_html__( 'Post published and sending emails to subscribers.', 'jetpack' )
195
			) . $view_post_link_html;
196
		return $messages;
197
	}
198
199
	public function should_email_post_to_subscribers( $post ) {
200
		$should_email = true;
201
		if ( get_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', true ) ) {
202
			return false;
203
		}
204
205
		// Only posts are currently supported
206
		if ( $post->post_type !== 'post' ) {
207
			return false;
208
		}
209
210
		// Private posts are not sent to subscribers.
211
		if ( 'private' === $post->post_status ) {
212
			return false;
213
		}
214
215
		/**
216
		 * Array of categories that will never trigger subscription emails.
217
		 *
218
		 * Will not send subscription emails from any post from within these categories.
219
		 *
220
		 * @module subscriptions
221
		 *
222
		 * @since 3.7.0
223
		 *
224
		 * @param array $args Array of category slugs or ID's.
225
		 */
226
		$excluded_categories = apply_filters( 'jetpack_subscriptions_exclude_these_categories', array() );
227
228
		// Never email posts from these categories
229
		if ( ! empty( $excluded_categories ) && in_category( $excluded_categories, $post->ID ) ) {
230
			$should_email = false;
231
		}
232
233
		/**
234
		 * ONLY send subscription emails for these categories
235
		 *
236
		 * Will ONLY send subscription emails to these categories.
237
		 *
238
		 * @module subscriptions
239
		 *
240
		 * @since 3.7.0
241
		 *
242
		 * @param array $args Array of category slugs or ID's.
243
		 */
244
		$only_these_categories = apply_filters( 'jetpack_subscriptions_exclude_all_categories_except', array() );
245
246
		// Only emails posts from these categories
247
		if ( ! empty( $only_these_categories ) && ! in_category( $only_these_categories, $post->ID ) ) {
248
			$should_email = false;
249
		}
250
251
		return $should_email;
252
	}
253
254
	function set_post_flags( $flags, $post ) {
255
		$flags['send_subscription'] = $this->should_email_post_to_subscribers( $post );
256
		return $flags;
257
	}
258
259
	/**
260
	 * Jetpack_Subscriptions::configure()
261
	 *
262
	 * Jetpack Subscriptions configuration screen.
263
	 */
264
	function configure() {
265
		// Create the section
266
		add_settings_section(
267
			'jetpack_subscriptions',
268
			__( 'Jetpack Subscriptions Settings', 'jetpack' ),
269
			array( $this, 'subscriptions_settings_section' ),
270
			'discussion'
271
		);
272
273
		/** Subscribe to Posts ***************************************************/
274
275
		add_settings_field(
276
			'jetpack_subscriptions_post_subscribe',
277
			__( 'Follow Blog', 'jetpack' ),
278
			array( $this, 'subscription_post_subscribe_setting' ),
279
			'discussion',
280
			'jetpack_subscriptions'
281
		);
282
283
		register_setting(
284
			'discussion',
285
			'stb_enabled'
286
		);
287
288
		/** Subscribe to Comments ******************************************************/
289
290
		add_settings_field(
291
			'jetpack_subscriptions_comment_subscribe',
292
			__( 'Follow Comments', 'jetpack' ),
293
			array( $this, 'subscription_comment_subscribe_setting' ),
294
			'discussion',
295
			'jetpack_subscriptions'
296
		);
297
298
		register_setting(
299
			'discussion',
300
			'stc_enabled'
301
		);
302
303
		/** Email me whenever: Someone follows my blog ***************************************************/
304
		/* @since 8.1 */
305
306
		add_settings_section(
307
			'notifications_section',
308
			__( 'Someone follows my blog', 'jetpack' ),
309
			array( $this, 'social_notifications_subscribe_section' ),
310
			'discussion'
311
		);
312
313
		add_settings_field(
314
			'jetpack_subscriptions_social_notifications_subscribe',
315
			__( 'Email me whenever', 'jetpack' ),
316
			array( $this, 'social_notifications_subscribe_field' ),
317
			'discussion',
318
			'notifications_section'
319
		);
320
321
		register_setting(
322
			'discussion',
323
			'social_notifications_subscribe',
324
			array( $this, 'social_notifications_subscribe_validate' )
325
		);
326
327
		/** Subscription Messaging Options ******************************************************/
328
329
		register_setting(
330
			'reading',
331
			'subscription_options',
332
			array( $this, 'validate_settings' )
333
		);
334
335
		add_settings_section(
336
			'email_settings',
337
			__( 'Follower Settings', 'jetpack' ),
338
			array( $this, 'reading_section' ),
339
			'reading'
340
		);
341
342
		add_settings_field(
343
			'invitation',
344
			__( 'Blog follow email text', 'jetpack' ),
345
			array( $this, 'setting_invitation' ),
346
			'reading',
347
			'email_settings'
348
		);
349
350
		add_settings_field(
351
			'comment-follow',
352
			__( 'Comment follow email text', 'jetpack' ),
353
			array( $this, 'setting_comment_follow' ),
354
			'reading',
355
			'email_settings'
356
		);
357
	}
358
359
	/**
360
	 * Discussions setting section blurb
361
	 *
362
	 */
363
	function subscriptions_settings_section() {
364
	?>
365
		<p id="jetpack-subscriptions-settings"><?php _e( 'Change whether your visitors can subscribe to your posts or comments or both.', 'jetpack' ); ?></p>
366
367
	<?php
368
	}
369
370
	/**
371
	 * Post Subscriptions Toggle
372
	 *
373
	 */
374 View Code Duplication
	function subscription_post_subscribe_setting() {
375
376
		$stb_enabled = get_option( 'stb_enabled', 1 ); ?>
377
378
		<p class="description">
379
			<input type="checkbox" name="stb_enabled" id="jetpack-post-subscribe" value="1" <?php checked( $stb_enabled, 1 ); ?> />
380
			<?php _e( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ); ?>
381
		</p>
382
	<?php
383
	}
384
385
	/**
386
	 * Comments Subscriptions Toggle
387
	 *
388
	 */
389 View Code Duplication
	function subscription_comment_subscribe_setting() {
390
391
		$stc_enabled = get_option( 'stc_enabled', 1 ); ?>
392
393
		<p class="description">
394
			<input type="checkbox" name="stc_enabled" id="jetpack-comment-subscribe" value="1" <?php checked( $stc_enabled, 1 ); ?> />
395
			<?php _e( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ); ?>
396
		</p>
397
398
	<?php
399
	}
400
401
	/**
402
	 * Someone follows my blog section
403
	 *
404
	 * @since 8.1
405
	 */
406
	public function social_notifications_subscribe_section() {
407
		// Atypical usage here. We emit jquery to move subscribe notification checkbox to be with the rest of the email notification settings
408
		?>
409
		<script type="text/javascript">
410
			jQuery( function( $ )  {
411
				var table = $( '#social_notifications_subscribe' ).parents( 'table:first' ),
412
					header = table.prevAll( 'h2:first' ),
413
					newParent = $( '#moderation_notify' ).parent( 'label' ).parent();
414
415
				if ( ! table.length || ! header.length || ! newParent.length ) {
416
					return;
417
				}
418
419
				newParent.append( '<br/>' ).append( table.end().parent( 'label' ).siblings().andSelf() );
420
				header.remove();
421
				table.remove();
422
			} );
423
		</script>
424
		<?php
425
	}
426
427
	/**
428
	 * Someone follows my blog Toggle
429
	 *
430
	 * @since 8.1
431
	 */
432
	public function social_notifications_subscribe_field() {
433
		$checked = intval( 'on' === get_option( 'social_notifications_subscribe', 'on' ) );
434
		?>
435
436
		<label>
437
			<input type="checkbox" name="social_notifications_subscribe" id="social_notifications_subscribe" value="1" <?php checked( $checked ); ?> />
438
			<?php
439
				/* translators: this is a label for a setting that starts with "Email me whenever" */
440
				esc_html_e( 'Someone follows my blog', 'jetpack' );
441
			?>
442
		</label>
443
		<?php
444
	}
445
446
	/**
447
	 * Validate "Someone follows my blog" option
448
	 *
449
	 * @since 8.1
450
	 *
451
	 * @param String $input the input string to be validated.
452
	 * @return string on|off
453
	 */
454
	public function social_notifications_subscribe_validate( $input ) {
455
		// If it's not set (was unchecked during form submission) or was set to off (during option update), return 'off'.
456
		if ( ! $input || 'off' === $input ) {
457
			return 'off';
458
		}
459
460
		// Otherwise we return 'on'.
461
		return 'on';
462
	}
463
464
	function validate_settings( $settings ) {
465
		global $allowedposttags;
466
467
		$default = $this->get_default_settings();
468
469
		// Blog Follow
470
		$settings['invitation'] = trim( wp_kses( $settings['invitation'], $allowedposttags ) );
471
		if ( empty( $settings['invitation'] ) )
472
			$settings['invitation'] = $default['invitation'];
473
474
		// Comments Follow (single post)
475
		$settings['comment_follow'] = trim( wp_kses( $settings['comment_follow'], $allowedposttags ) );
476
		if ( empty( $settings['comment_follow'] ) )
477
			$settings['comment_follow'] = $default['comment_follow'];
478
479
		return $settings;
480
	}
481
482
	public function reading_section() {
483
		echo '<p id="follower-settings">';
484
		_e( 'These settings change emails sent from your blog to followers.', 'jetpack' );
485
		echo '</p>';
486
	}
487
488
	public function setting_invitation() {
489
		$settings = $this->get_settings();
490
		echo '<textarea name="subscription_options[invitation]" class="large-text" cols="50" rows="5">' . esc_textarea( $settings['invitation'] ) . '</textarea>';
491
		echo '<p><span class="description">'.__( 'Introduction text sent when someone follows your blog. (Site and confirmation details will be automatically added for you.)', 'jetpack' ).'</span></p>';
492
	}
493
494
	public function setting_comment_follow() {
495
		$settings = $this->get_settings();
496
		echo '<textarea name="subscription_options[comment_follow]" class="large-text" cols="50" rows="5">' . esc_textarea( $settings['comment_follow'] ) . '</textarea>';
497
		echo '<p><span class="description">'.__( 'Introduction text sent when someone follows a post on your blog. (Site and confirmation details will be automatically added for you.)', 'jetpack' ).'</span></p>';
498
	}
499
500
	function get_default_settings() {
501
		return array(
502
			'invitation'     => __( "Howdy.\n\nYou recently followed this blog's posts. This means you will receive each new post by email.\n\nTo activate, click confirm below. If you believe this is an error, ignore this message and we'll never bother you again.", 'jetpack' ),
503
			'comment_follow' => __( "Howdy.\n\nYou recently followed one of my posts. This means you will receive an email when new comments are posted.\n\nTo activate, click confirm below. If you believe this is an error, ignore this message and we'll never bother you again.", 'jetpack' )
504
		);
505
	}
506
507
	function get_settings() {
508
		return wp_parse_args( (array) get_option( 'subscription_options', array() ), $this->get_default_settings() );
509
	}
510
511
	/**
512
	 * Jetpack_Subscriptions::subscribe()
513
	 *
514
	 * Send a synchronous XML-RPC subscribe to blog posts or subscribe to post comments request.
515
	 *
516
	 * @param string $email
517
	 * @param array  $post_ids (optional) defaults to 0 for blog posts only: array of post IDs to subscribe to blog's posts
0 ignored issues
show
Documentation introduced by
Should the type for parameter $post_ids not be integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
518
	 * @param bool   $async    (optional) Should the subscription be performed asynchronously?  Defaults to true.
519
	 *
520
	 * @return true|Jetpack_Error true on success
521
	 *	invalid_email   : not a valid email address
522
	 *	invalid_post_id : not a valid post ID
523
	 *	unknown_post_id : unknown post
524
	 *	not_subscribed  : strange error.  Jetpack servers at WordPress.com could subscribe the email.
525
	 *	disabled        : Site owner has disabled subscriptions.
526
	 *	active          : Already subscribed.
527
	 *	pending         : Tried to subscribe before but the confirmation link is never clicked. No confirmation email is sent.
528
	 *	unknown         : strange error.  Jetpack servers at WordPress.com returned something malformed.
529
	 *	unknown_status  : strange error.  Jetpack servers at WordPress.com returned something I didn't understand.
530
	 */
531
	function subscribe( $email, $post_ids = 0, $async = true, $extra_data = array() ) {
532
		if ( !is_email( $email ) ) {
533
			return new Jetpack_Error( 'invalid_email' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'invalid_email'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
534
		}
535
536
		if ( !$async ) {
537
			$xml = new Jetpack_IXR_ClientMulticall();
538
		}
539
540
		foreach ( (array) $post_ids as $post_id ) {
541
			$post_id = (int) $post_id;
542
			if ( $post_id < 0 ) {
543
				return new Jetpack_Error( 'invalid_post_id' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'invalid_post_id'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
544
			} else if ( $post_id && !$post = get_post( $post_id ) ) {
545
				return new Jetpack_Error( 'unknown_post_id' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'unknown_post_id'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
546
			}
547
548
			if ( $async ) {
549
				Jetpack::xmlrpc_async_call( 'jetpack.subscribeToSite', $email, $post_id, serialize( $extra_data ) );
550
			} else {
551
				$xml->addCall( 'jetpack.subscribeToSite', $email, $post_id, serialize( $extra_data ) );
0 ignored issues
show
Bug introduced by
The variable $xml does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
552
			}
553
		}
554
555
		if ( $async ) {
556
			return;
557
		}
558
559
		// Call
560
		$xml->query();
561
562
		if ( $xml->isError() ) {
563
			return $xml->get_jetpack_error();
564
		}
565
566
		$responses = $xml->getResponse();
567
568
		$r = array();
569
		foreach ( (array) $responses as $response ) {
570
			if ( isset( $response['faultCode'] ) || isset( $response['faultString'] ) ) {
571
				$r[] = $xml->get_jetpack_error( $response['faultCode'], $response['faultString'] );
572
				continue;
573
			}
574
575
			if ( !is_array( $response[0] ) || empty( $response[0]['status'] ) ) {
576
				$r[] = new Jetpack_Error( 'unknown' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'unknown'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
577
				continue;
578
			}
579
580
			switch ( $response[0]['status'] ) {
581
				case 'error':
582
					$r[] = new Jetpack_Error( 'not_subscribed' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'not_subscribed'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
583
					continue 2;
584
				case 'disabled':
585
					$r[] = new Jetpack_Error( 'disabled' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'disabled'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
586
					continue 2;
587
				case 'active':
588
					$r[] = new Jetpack_Error( 'active' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'active'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
589
					continue 2;
590
				case 'confirming':
591
					$r[] = true;
592
					continue 2;
593
				case 'pending':
594
					$r[] = new Jetpack_Error( 'pending' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'pending'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
595
					continue 2;
596
				default:
597
					$r[] = new Jetpack_Error( 'unknown_status', (string) $response[0]['status'] );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'unknown_status'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
598
					continue 2;
599
			}
600
		}
601
602
		return $r;
603
	}
604
605
	/**
606
	 * Jetpack_Subscriptions::widget_submit()
607
	 *
608
	 * When a user submits their email via the blog subscription widget, check the details and call the subsribe() method.
609
	 */
610
	function widget_submit() {
611
		// Check the nonce.
612
		if ( is_user_logged_in() ) {
613
			check_admin_referer( 'blogsub_subscribe_' . get_current_blog_id() );
614
		}
615
616
		if ( empty( $_REQUEST['email'] ) )
617
			return false;
618
619
		$redirect_fragment = false;
620
		if ( isset( $_REQUEST['redirect_fragment'] ) ) {
621
			$redirect_fragment = preg_replace( '/[^a-z0-9_-]/i', '', $_REQUEST['redirect_fragment'] );
622
		}
623
		if ( !$redirect_fragment ) {
624
			$redirect_fragment = 'subscribe-blog';
625
		}
626
627
		$subscribe = Jetpack_Subscriptions::subscribe(
628
												$_REQUEST['email'],
629
												0,
630
												false,
631
												array(
632
													'source'         => 'widget',
633
													'widget-in-use'  => is_active_widget( false, false, 'blog_subscription', true ) ? 'yes' : 'no',
634
													'comment_status' => '',
635
													'server_data'    => jetpack_subscriptions_cherry_pick_server_data(),
636
												)
637
		);
638
639
		if ( is_wp_error( $subscribe ) ) {
640
			$error = $subscribe->get_error_code();
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
641
		} else {
642
			$error = false;
643
			foreach ( $subscribe as $response ) {
0 ignored issues
show
Bug introduced by
The expression $subscribe of type null|object<WP_Error>|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
644
				if ( is_wp_error( $response ) ) {
645
					$error = $response->get_error_code();
646
					break;
647
				}
648
			}
649
		}
650
651
		switch ( $error ) {
652
			case false:
653
				$result = 'success';
654
				break;
655
			case 'invalid_email':
656
				$result = $error;
657
				break;
658
			case 'blocked_email':
659
				$result = 'opted_out';
660
				break;
661
			case 'active':
662
				$result = 'already';
663
				break;
664
			case 'flooded_email':
665
				$result = 'many_pending_subs';
666
				break;
667
			case 'pending':
668
				$result = 'pending';
669
				break;
670
			default:
671
				$result = 'error';
672
				break;
673
		}
674
675
		$redirect = add_query_arg( 'subscribe', $result );
676
677
		/**
678
		 * Fires on each subscription form submission.
679
		 *
680
		 * @module subscriptions
681
		 *
682
		 * @since 3.7.0
683
		 *
684
		 * @param string $result Result of form submission: success, invalid_email, already, error.
685
		 */
686
		do_action( 'jetpack_subscriptions_form_submission', $result );
687
688
		wp_safe_redirect( "$redirect#$redirect_fragment" );
689
		exit;
690
	}
691
692
	/**
693
	 * Jetpack_Subscriptions::comment_subscribe_init()
694
	 *
695
	 * Set up and add the comment subscription checkbox to the comment form.
696
	 *
697
	 * @param string $submit_button HTML markup for the submit field.
698
	 * @param array  $args          Arguments passed to `comment_form()`.
699
	 */
700
	function comment_subscribe_init( $submit_button, $args ) {
701
		global $post;
702
703
		$comments_checked = '';
704
		$blog_checked     = '';
705
706
		// Check for a comment / blog submission and set a cookie to retain the setting and check the boxes.
707
		if ( isset( $_COOKIE[ 'jetpack_comments_subscribe_' . self::$hash . '_' . $post->ID ] ) ) {
708
			$comments_checked = ' checked="checked"';
709
		}
710
711
		if ( isset( $_COOKIE[ 'jetpack_blog_subscribe_' . self::$hash ] ) ) {
712
			$blog_checked = ' checked="checked"';
713
		}
714
715
		// Some themes call this function, don't show the checkbox again
716
		remove_action( 'comment_form', 'subscription_comment_form' );
717
718
		// Check if Mark Jaquith's Subscribe to Comments plugin is active - if so, suppress Jetpack checkbox
719
720
		$str = '';
721
722
		if ( FALSE === has_filter( 'comment_form', 'show_subscription_checkbox' ) && 1 == get_option( 'stc_enabled', 1 ) && empty( $post->post_password ) && 'post' == get_post_type() ) {
723
			// Subscribe to comments checkbox
724
			$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 . ' /> ';
725
			$comment_sub_text = __( 'Notify me of follow-up comments by email.', 'jetpack' );
726
			$str .=	'<label class="subscribe-label" id="subscribe-label" for="subscribe_comments">' . esc_html(
727
				/**
728
				 * Filter the Subscribe to comments text appearing below the comment form.
729
				 *
730
				 * @module subscriptions
731
				 *
732
				 * @since 3.4.0
733
				 *
734
				 * @param string $comment_sub_text Subscribe to comments text.
735
				 */
736
				apply_filters( 'jetpack_subscribe_comment_label', $comment_sub_text )
737
			) . '</label>';
738
			$str .= '</p>';
739
		}
740
741
		if ( 1 == get_option( 'stb_enabled', 1 ) ) {
742
			// Subscribe to blog checkbox
743
			$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 . ' /> ';
744
			$blog_sub_text = __( 'Notify me of new posts by email.', 'jetpack' );
745
			$str .=	'<label class="subscribe-label" id="subscribe-blog-label" for="subscribe_blog">' . esc_html(
746
				/**
747
				 * Filter the Subscribe to blog 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 blog text.
754
				 */
755
				apply_filters( 'jetpack_subscribe_blog_label', $blog_sub_text )
756
			) . '</label>';
757
			$str .= '</p>';
758
		}
759
760
		/**
761
		 * Filter the output of the subscription options appearing below the comment form.
762
		 *
763
		 * @module subscriptions
764
		 *
765
		 * @since 1.2.0
766
		 *
767
		 * @param string $str Comment Subscription form HTML output.
768
		 */
769
		$str = apply_filters( 'jetpack_comment_subscription_form', $str );
770
771
		return $str . $submit_button;
772
	}
773
774
	/**
775
	 * Jetpack_Subscriptions::comment_subscribe_init()
776
	 *
777
	 * When a user checks the comment subscribe box and submits a comment, subscribe them to the comment thread.
778
	 */
779
	function comment_subscribe_submit( $comment_id, $approved ) {
780
		if ( 'spam' === $approved ) {
781
			return;
782
		}
783
784
		$comment = get_comment( $comment_id );
785
786
		// Set cookies for this post/comment
787
		$this->set_cookies( isset( $_REQUEST['subscribe_comments'] ), $comment->comment_post_ID, isset( $_REQUEST['subscribe_blog'] ) );
788
789
		if ( !isset( $_REQUEST['subscribe_comments'] ) && !isset( $_REQUEST['subscribe_blog'] ) )
790
			return;
791
792
		$post_ids = array();
793
794
		if ( isset( $_REQUEST['subscribe_comments'] ) )
795
			$post_ids[] = $comment->comment_post_ID;
796
797
		if ( isset( $_REQUEST['subscribe_blog'] ) )
798
			$post_ids[] = 0;
799
800
		$result = Jetpack_Subscriptions::subscribe(
801
									$comment->comment_author_email,
802
									$post_ids,
0 ignored issues
show
Documentation introduced by
$post_ids is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
803
									true,
804
									array(
805
										'source'         => 'comment-form',
806
										'widget-in-use'  => is_active_widget( false, false, 'blog_subscription', true ) ? 'yes' : 'no',
807
										'comment_status' => $approved,
808
										'server_data'    => jetpack_subscriptions_cherry_pick_server_data(),
809
									)
810
		);
811
812
		/**
813
		 * Fires on each comment subscription form submission.
814
		 *
815
		 * @module subscriptions
816
		 *
817
		 * @since 5.5.0
818
		 *
819
		 * @param NULL|WP_Error $result Result of form submission: NULL on success, WP_Error otherwise.
820
		 * @param Array $post_ids An array of post IDs that the user subscribed to, 0 means blog subscription.
821
		 */
822
		do_action( 'jetpack_subscriptions_comment_form_submission', $result, $post_ids );
823
	}
824
825
	/**
826
	 * Jetpack_Subscriptions::set_cookies()
827
	 *
828
	 * Set a cookie to save state on the comment and post subscription checkboxes.
829
	 *
830
	 * @param bool $subscribe_to_post Whether the user chose to subscribe to subsequent comments on this post.
831
	 * @param int $post_id If $subscribe_to_post is true, the post ID they've subscribed to.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $post_id not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
832
	 * @param bool $subscribe_to_blog Whether the user chose to subscribe to all new posts on the blog.
833
	 */
834
	function set_cookies( $subscribe_to_post = false, $post_id = null, $subscribe_to_blog = false ) {
835
		$post_id = intval( $post_id );
836
837
		/** This filter is already documented in core/wp-includes/comment-functions.php */
838
		$cookie_lifetime = apply_filters( 'comment_cookie_lifetime',       30000000 );
839
840
		/**
841
		 * Filter the Jetpack Comment cookie path.
842
		 *
843
		 * @module subscriptions
844
		 *
845
		 * @since 2.5.0
846
		 *
847
		 * @param string COOKIEPATH Cookie path.
848
		 */
849
		$cookie_path     = apply_filters( 'jetpack_comment_cookie_path',   COOKIEPATH );
850
851
		/**
852
		 * Filter the Jetpack Comment cookie domain.
853
		 *
854
		 * @module subscriptions
855
		 *
856
		 * @since 2.5.0
857
		 *
858
		 * @param string COOKIE_DOMAIN Cookie domain.
859
		 */
860
		$cookie_domain   = apply_filters( 'jetpack_comment_cookie_domain', COOKIE_DOMAIN );
861
862
		if ( $subscribe_to_post && $post_id >= 0 ) {
863
			setcookie( 'jetpack_comments_subscribe_' . self::$hash . '_' . $post_id, 1, time() + $cookie_lifetime, $cookie_path, $cookie_domain );
864
		} else {
865
			setcookie( 'jetpack_comments_subscribe_' . self::$hash . '_' . $post_id, '', time() - 3600, $cookie_path, $cookie_domain );
866
		}
867
868
		if ( $subscribe_to_blog ) {
869
			setcookie( 'jetpack_blog_subscribe_' . self::$hash, 1, time() + $cookie_lifetime, $cookie_path, $cookie_domain );
870
		} else {
871
			setcookie( 'jetpack_blog_subscribe_' . self::$hash, '', time() - 3600, $cookie_path, $cookie_domain );
872
		}
873
	}
874
875
	/**
876
	 * Set the social_notifications_subscribe option to `off` when the Subscriptions module is activated in the first time.
877
	 *
878
	 * @since 8.1
879
	 *
880
	 * @return null
881
	 */
882
	function set_social_notifications_subscribe() {
883
		if ( false === get_option( 'social_notifications_subscribe' ) ) {
884
			add_option( 'social_notifications_subscribe', 'off' );
885
		}
886
	}
887
888
}
889
890
Jetpack_Subscriptions::init();
891
892
include dirname( __FILE__ ) . '/subscriptions/views.php';
893