Completed
Push — add/implement_pre_connection_j... ( def69c...959dbf )
by
unknown
06:40
created

Message::max_dismissals()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Jetpack's JITM Message class.
4
 *
5
 * @package automattic/jetpack-jitm
6
 */
7
8
namespace Automattic\Jetpack\JITMS;
9
10
/**
11
 * Class JITM\Message
12
 *
13
 * Represents a message the client should display
14
 */
15
class Message {
16
	/**
17
	 * Message ID
18
	 *
19
	 * @var string
20
	 */
21
	public $id;
22
23
	/**
24
	 * Template
25
	 *
26
	 * @var string
27
	 */
28
	protected $template;
29
30
	/**
31
	 * Hosted with partner name
32
	 *
33
	 * @var string
34
	 */
35
	protected $hosted_with_partner;
36
37
	/**
38
	 * Inactive plugins
39
	 *
40
	 * @var array
41
	 */
42
	protected $inactive_plugins;
43
44
	/**
45
	 * Active plugins
46
	 *
47
	 * @var array
48
	 */
49
	protected $active_plugins;
50
51
	/**
52
	 * Installed plugins
53
	 *
54
	 * @var array
55
	 */
56
	protected $installed_plugins;
57
58
	/**
59
	 * Uninstalled plugins
60
	 *
61
	 * @var array
62
	 */
63
	protected $uninstalled_plugins;
64
65
	/**
66
	 * User roles
67
	 *
68
	 * @var array
69
	 */
70
	protected $roles;
71
72
	/**
73
	 * Message content
74
	 *
75
	 * @var string
76
	 */
77
	protected $content;
78
79
	/**
80
	 * Message path regular expression
81
	 *
82
	 * @var string
83
	 */
84
	protected $message_path_regex;
85
86
	/**
87
	 * Call to action
88
	 *
89
	 * @var string
90
	 */
91
	protected $cta;
92
93
	/**
94
	 * Redux action
95
	 *
96
	 * @var string
97
	 */
98
	protected $redux_action;
99
100
	/**
101
	 * Query
102
	 *
103
	 * @var string
104
	 */
105
	protected $query;
106
107
	/**
108
	 * Message priority
109
	 *
110
	 * @var int
111
	 */
112
	protected $priority;
113
114
	/**
115
	 * Feature class
116
	 *
117
	 * @var string
118
	 */
119
	protected $feature_class;
120
121
	/**
122
	 * Max dismissals
123
	 *
124
	 * @var int
125
	 */
126
	protected $max_dismissals;
127
128
	/**
129
	 * Next show in
130
	 *
131
	 * @var int
132
	 */
133
	protected $next_show;
134
135
	/**
136
	 * Theme
137
	 *
138
	 * @var string
139
	 */
140
	protected $theme;
141
142
	/**
143
	 * Active widgets
144
	 *
145
	 * @var array
146
	 */
147
	protected $active_widgets;
148
149
	/**
150
	 * Inactive widgets
151
	 *
152
	 * @var array
153
	 */
154
	protected $inactive_widgets;
155
156
	/**
157
	 * Option matches
158
	 *
159
	 * @var array
160
	 */
161
	protected $option_matches;
162
163
	/**
164
	 * Uses mobile browser
165
	 *
166
	 * @var bool
167
	 */
168
	protected $mobile_browser;
169
170
	/**
171
	 * User locales
172
	 *
173
	 * @var array
174
	 */
175
	protected $user_locales;
176
177
	/**
178
	 * Message is dismissable
179
	 *
180
	 * @var bool
181
	 */
182
	protected $is_dismissible;
183
184
	/**
185
	 * Calculated score
186
	 *
187
	 * @var int
188
	 */
189
	protected $calculated_score;
190
191
	/**
192
	 * Class constructor
193
	 *
194
	 * @param string $id Message ID.
195
	 * @param string $feature_class Feature class.
196
	 */
197
	public function __construct( $id, $feature_class ) {
198
		$this->id                  = $id;
199
		$this->feature_class       = $feature_class;
200
		$this->template            = 'default'; // 'default-with-button' ...
201
		$this->max_dismissals      = 2;
202
		$this->next_show           = 3628800; // 6 weeks in seconds
203
		$this->inactive_plugins    = array();
204
		$this->active_plugins      = array();
205
		$this->installed_plugins   = array();
206
		$this->uninstalled_plugins = array();
207
		$this->roles               = array();
208
		$this->content             = array(
0 ignored issues
show
Documentation Bug introduced by
It seems like array('message' => '', '...ull, 'list' => array()) of type array<string,string|null..."null","list":"array"}> is incompatible with the declared type string of property $content.

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...
209
			'message' => '',
210
			'icon'    => null,
211
			'list'    => array(),
212
		);
213
		$this->query               = array();
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type string of property $query.

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...
214
		$this->message_path_regex  = null;
215
		$this->calculated_score    = 0;
216
		$this->cta                 = array(
0 ignored issues
show
Documentation Bug introduced by
It seems like array('message' => '', '...rue, 'primary' => true) of type array<string,string|null...","primary":"boolean"}> is incompatible with the declared type string of property $cta.

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...
217
			'message'   => '',
218
			'hook'      => null,
219
			'newWindow' => true,
220
			'primary'   => true,
221
		);
222
		$this->redux_action        = null;
223
		$this->priority            = 0;
224
		$this->hosted_with_partner = null;
225
		$this->theme               = null;
226
		$this->active_widgets      = array();
227
		$this->inactive_widgets    = array();
228
		$this->option_matches      = array();
229
		$this->mobile_browser      = null;
230
		$this->user_locales        = array();
231
		$this->is_dismissible      = true;
232
233
	}
234
235
	/**
236
	 * Open the CTA link in the same window instead of a new window
237
	 *
238
	 * @return $this
239
	 */
240
	public function open_cta_in_same_window() {
241
		$this->cta['newWindow'] = false;
242
243
		return $this;
244
	}
245
246
	/**
247
	 * Score the message path
248
	 *
249
	 * @param string $path path.
250
	 * @param int    $external_user_id external_user_id.
251
	 * @param array  $query query.
252
	 * @param int    $score score.
253
	 *
254
	 * @return bool|int
255
	 */
256
	private function score_message_path( $path, $external_user_id, $query, $score ) {
257
		if ( empty( $this->message_path_regex ) ) {
258
			return $score;
259
		}
260
261
		$score = (int) preg_match( $this->message_path_regex, $path );
262
263
		return $score ? $score : false;
264
	}
265
266
	/**
267
	 * Score the query string
268
	 *
269
	 * @param string $path path.
270
	 * @param int    $external_user_id external_user_id.
271
	 * @param array  $query query.
272
	 * @param int    $score score.
273
	 *
274
	 * @return bool|int
275
	 */
276
	private function score_query_string( $path, $external_user_id, $query, $score ) {
277
		if ( empty( $this->query ) ) {
278
			return $score;
279
		}
280
281
		$score = array_reduce(
282
			array_keys( $query ),
283
			function ( $score, $key ) use ( &$query ) {
284
				if ( $score ) {
285
					return $score;
286
				}
287
288
				if ( array_key_exists( $key, $this->query ) ) {
289
					return (int) preg_match( $this->query[ $key ], $query[ $key ] );
290
				}
291
292
				return 0;
293
			},
294
			0
295
		);
296
297
		return $score ? $score : false;
298
	}
299
300
	/**
301
	 * Score option matches
302
	 *
303
	 * @param string $path path.
304
	 * @param int    $external_user_id external_user_id.
305
	 * @param array  $query query.
306
	 * @param int    $score score.
307
	 *
308
	 * @return bool|int
309
	 */
310
	private function score_option_matches( $path, $external_user_id, $query, $score ) {
311
		if ( empty( $this->option_matches ) ) {
312
			return $score;
313
		}
314
315
		// If any of the matches are rejected, reject the rule entirely.
316
		if ( array_filter(
317
			$this->option_matches,
318
			function ( $match ) {
319
				return false === $match;
320
			}
321
		) ) {
322
			return false;
323
		}
324
325
		// If any of the matches are accepted with an integer, use the largest as the score.
326
		$ints = array_filter( $this->option_matches, 'is_int' );
327
		if ( $ints ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ints of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
328
			return max( $ints );
329
		}
330
331
		// Otherwise, accept the rule with score 1.
332
		return 1;
333
	}
334
335
	/**
336
	 * Score dismissal
337
	 *
338
	 * @param string $path path.
339
	 * @param int    $external_user_id external_user_id.
340
	 * @param array  $query query.
341
	 * @param int    $score score.
342
	 *
343
	 * @return bool|int
344
	 */
345
	private function score_dismissal( $path, $external_user_id, $query, $score ) {
346
		$dismissals = $this->get_dismissals();
347
		if ( false !== $dismissals && is_array( $dismissals ) && isset( $dismissals[ $this->feature_class ] ) && is_array( $dismissals[ $this->feature_class ] ) ) {
348
			$score = 0;
349
350
			$dismissal = $dismissals[ $this->feature_class ];
351
			if ( time() > $dismissal['last_dismissal'] + $this->next_show && $dismissal['number'] < $this->max_dismissals ) {
352
				$score = 1;
353
			}
354
355
			return $score ? $score : false;
356
		}
357
358
		return $score;
359
	}
360
361
	/**
362
	 * Score hosted with partner
363
	 *
364
	 * @param string $path path.
365
	 * @param int    $external_user_id external_user_id.
366
	 * @param array  $query query.
367
	 * @param int    $score score.
368
	 *
369
	 * @return bool|int
370
	 */
371
	private function score_hosted_with_partner( $path, $external_user_id, $query, $score ) {
372
		if ( is_null( $this->hosted_with_partner ) ) {
373
			return $score;
374
		}
375
376
		// Using 'bluehost' for development
377
		// TODO: replace with proper detection method.
378
379
		if ( 'bluehost' === $this->hosted_with_partner ) {
380
			return 1;
381
		}
382
383
		return false;
384
	}
385
386
	/**
387
	 * Score user locale
388
	 *
389
	 * @param string $path path.
390
	 * @param int    $external_user_id external_user_id.
391
	 * @param array  $query query.
392
	 * @param int    $score score.
393
	 *
394
	 * @return bool|int
395
	 */
396
	private function score_user_locale( $path, $external_user_id, $query, $score ) {
397
		if ( empty( $this->user_locales ) ) {
398
			return $score;
399
		}
400
401
		$user_locale = strtolower( get_user_locale() );
402
403
		return in_array( $user_locale, $this->user_locales, true );
404
	}
405
406
	/**
407
	 * Score user roles
408
	 *
409
	 * @param string $path path.
410
	 * @param int    $external_user_id external_user_id.
411
	 * @param array  $query query.
412
	 * @param int    $score score.
413
	 *
414
	 * @return bool|int
415
	 */
416
	private function score_user_roles( $path, $external_user_id, $query, $score ) {
417
		if ( empty( $this->roles ) ) {
418
			return $score;
419
		}
420
421
		$user = wp_get_current_user();
422
		foreach ( $this->roles as $cap ) {
423
			if ( in_array( $cap, $user->roles, true ) ) {
424
				return true;
425
			}
426
		}
427
428
		return false;
429
	}
430
431
	/**
432
	 * Score user theme
433
	 *
434
	 * @param string $path path.
435
	 * @param int    $external_user_id external_user_id.
436
	 * @param array  $query query.
437
	 * @param int    $score score.
438
	 *
439
	 * @return bool|int
440
	 */
441
	private function score_user_theme( $path, $external_user_id, $query, $score ) {
442
		if ( null === $this->theme ) {
443
			return $score;
444
		}
445
446
		$active_theme = $this->get_or_set(
0 ignored issues
show
Bug introduced by
The method get_or_set() does not seem to exist on object<Automattic\Jetpack\JITMS\Message>.

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...
447
			'themes',
448
			'active',
449
			function () {
450
				return array_pop( explode( '/', wp_get_theme()->get_stylesheet() ) );
0 ignored issues
show
Bug introduced by
explode('/', wp_get_theme()->get_stylesheet()) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
451
			}
452
		);
453
454
		foreach ( $this->theme as $theme ) {
0 ignored issues
show
Bug introduced by
The expression $this->theme of type string is not traversable.
Loading history...
455
			if ( preg_match( $theme, $active_theme ) > 0 ) {
456
				return 1;
457
			}
458
		}
459
460
		return false;
461
	}
462
463
	/**
464
	 * Score plugins
465
	 *
466
	 * @param string $path path.
467
	 * @param int    $external_user_id external_user_id.
468
	 * @param array  $query query.
469
	 * @param int    $score score.
470
	 *
471
	 * @return bool|int
472
	 */
473
	private function score_plugins( $path, $external_user_id, $query, $score ) {
474
		if ( ! empty( $this->active_plugins ) || ! empty( $this->inactive_plugins ) || ! empty( $this->installed_plugins ) || ! empty( $this->uninstalled_plugins ) ) {
475
			$installed_plugins = $this->get_installed_plugins();
476
			$active_plugins    = $this->get_active_plugins();
477
478
			// check for inactive plugins.
479
			$score = (int) array_reduce(
480
				$this->inactive_plugins,
481 View Code Duplication
				function ( $score, $inactive_plugin ) use ( &$active_plugins, &$installed_plugins ) {
482
					if ( true !== $score && $score > 0 ) {
483
						return $score; // this creates an OR condition.
484
					}
485
486
					if ( isset( $installed_plugins[ $inactive_plugin ] ) && in_array( $inactive_plugin, $active_plugins, true ) ) {
487
						return 0;
488
					}
489
490
					return 1;
491
				},
492
				true
493
			);
494
495
			if ( ! $score ) {
496
				return false;
497
			}
498
499
			// check for active plugins.
500
			$score = (int) array_reduce(
501
				$this->active_plugins,
502 View Code Duplication
				function ( $score, $active_plugin ) use ( &$active_plugins, &$installed_plugins ) {
503
					if ( true !== $score && $score > 0 ) {
504
						return $score; // this creates an OR condition.
505
					}
506
507
					if ( isset( $installed_plugins[ $active_plugin ] ) && in_array( $active_plugin, $active_plugins, true ) ) {
508
						return 1;
509
					}
510
511
					return 0;
512
				},
513
				true
514
			);
515
516
			if ( ! $score ) {
517
				return false;
518
			}
519
520
			// check for installed plugins.
521
			$score = (int) array_reduce(
522
				$this->installed_plugins,
523 View Code Duplication
				function ( $score, $plugin ) use ( &$installed_plugins ) {
524
					if ( true !== $score && $score > 0 ) {
525
						return $score;
526
					}
527
528
					if ( isset( $installed_plugins[ $plugin ] ) ) {
529
						return 1;
530
					}
531
532
					return 0;
533
				},
534
				true
535
			);
536
537
			if ( ! $score ) {
538
				return false; // 0
539
			}
540
541
			$score = (int) array_reduce(
542
				$this->uninstalled_plugins,
543 View Code Duplication
				function ( $score, $plugin ) use ( &$installed_plugins ) {
544
					if ( true !== $score && $score > 0 ) {
545
						return $score;
546
					}
547
548
					if ( isset( $installed_plugins[ $plugin ] ) ) {
549
						return 0;
550
					}
551
552
					return 1;
553
				},
554
				true
555
			);
556
557
			if ( ! $score ) {
558
				return false; // 0
559
			}
560
		}
561
562
		return $score;
563
	}
564
565
	/**
566
	 * Score active widgets
567
	 *
568
	 * @param string $path path.
569
	 * @param int    $external_user_id external_user_id.
570
	 * @param array  $query query.
571
	 * @param int    $score score.
572
	 *
573
	 * @return bool|int
574
	 */
575 View Code Duplication
	private function score_active_widgets( $path, $external_user_id, $query, $score ) {
576
		if ( empty( $this->active_widgets ) ) {
577
			return $score;
578
		}
579
		$active_widget_list = $this->get_widget_list();
580
581
		foreach ( $this->active_widgets as $active_widget ) {
582
			if ( in_array( $active_widget, $active_widget_list, true ) ) {
583
				return 1;
584
			}
585
		}
586
587
		return false;
588
	}
589
590
	/**
591
	 * Score inactive widgets
592
	 *
593
	 * @param string $path path.
594
	 * @param int    $external_user_id external_user_id.
595
	 * @param array  $query query.
596
	 * @param int    $score score.
597
	 *
598
	 * @return bool|int
599
	 */
600 View Code Duplication
	private function score_inactive_widgets( $path, $external_user_id, $query, $score ) {
601
		if ( empty( $this->inactive_widgets ) ) {
602
			return $score;
603
		}
604
605
		$active_widget_list = $this->get_widget_list();
606
607
		foreach ( $this->inactive_widgets as $inactive_widget ) {
608
			if ( in_array( $inactive_widget, $active_widget_list, true ) ) {
609
				return false;
610
			}
611
		}
612
613
		return 1;
614
	}
615
616
	/**
617
	 * Score mobile browser
618
	 *
619
	 * @param string $path path.
620
	 * @param int    $external_user_id external_user_id.
621
	 * @param array  $query query.
622
	 * @param int    $score score.
623
	 * @param bool   $mobile_browser mobile browser.
624
	 *
625
	 * @return bool|int
626
	 */
627
	private function score_mobile_browser( $path, $external_user_id, $query, $score, $mobile_browser ) {
628
		if ( is_null( $this->mobile_browser ) ) {
629
			return $score;
630
		}
631
632
		if ( $this->mobile_browser === $mobile_browser ) {
633
			return 1;
634
		}
635
636
		return false;
637
	}
638
639
	/**
640
	 * Calculates the score of the jitm message
641
	 *
642
	 * The goal is to return a score of 0 if anything fails to match, and a score > 1 for all matches.
643
	 *
644
	 * @param string $path The message path from the user's browser.
645
	 * @param int    $external_user_id The external user id.
646
	 * @param array  $external_caps The external user's role.
647
	 * @param array  $query The query string from the browser.
648
	 * @param bool   $mobile_browser Is using a mobile browser.
649
	 *
650
	 * @return int The score for this jitm
651
	 */
652
	public function score( $path, $external_user_id, $external_caps, $query, $mobile_browser ) {
653
		$score = 0;
654
655
		// try and keep this in order of least expensive to most expensive - in terms of db/transaction overhead.
656
		$score_priority = array(
657
			'score_message_path',
658
			'score_query_string',
659
			'score_mobile_browser',
660
			'score_option_matches',
661
			'score_dismissal',
662
			'score_user_roles',
663
			'score_user_theme',
664
			'score_plugins',
665
			'score_active_widgets',
666
			'score_inactive_widgets',
667
			'score_hosted_with_partner',
668
		);
669
670
		foreach ( $score_priority as $score_func ) {
671
			$score = $this->$score_func( $path, $external_user_id, $query, $score, $mobile_browser );
672
			if ( false === $score ) {
673
				return 0;
674
			}
675
		}
676
677
		return $score + $this->priority;
678
	}
679
680
	/**
681
	 * Renders the internal state to a simple object
682
	 *
683
	 * @return \stdClass The simple object
684
	 */
685
	public function render() {
686 View Code Duplication
		if ( ! is_string( $this->content['message'] ) && is_callable( $this->content['message'] ) ) {
687
			$cb                       = $this->content['message'];
688
			$this->content['message'] = $cb();
689
		}
690
691 View Code Duplication
		if ( isset( $this->content['description'] ) && ! is_string( $this->content['description'] ) && is_callable( $this->content['description'] ) ) {
692
			$cb                           = $this->content['description'];
693
			$this->content['description'] = $cb();
694
		}
695
696
		if ( isset( $this->content['list'] ) ) {
697
			foreach ( $this->content['list'] as &$list ) {
0 ignored issues
show
Bug introduced by
The expression $this->content['list'] of type string is not traversable.
Loading history...
698
				if ( ! is_string( $list ) && is_callable( $list ) ) {
699
					$list = $list();
700
				}
701
			}
702
		}
703
704 View Code Duplication
		if ( ! is_string( $this->cta['message'] ) && is_callable( $this->cta['message'] ) ) {
705
			$cb                   = $this->cta['message'];
706
			$this->cta['message'] = $cb();
707
		}
708
709 View Code Duplication
		if ( ! is_string( $this->cta['link'] ) && is_callable( $this->cta['link'] ) ) {
710
			$cb                = $this->cta['link'];
711
			$this->cta['link'] = $cb();
712
		}
713
714
		$obj                 = new \stdClass();
715
		$obj->content        = $this->content;
716
		$obj->cta            = $this->cta;
717
		$obj->template       = $this->template;
718
		$obj->id             = $this->id;
719
		$obj->feature_class  = $this->feature_class;
720
		$obj->expires        = $this->next_show;
721
		$obj->max_dismissal  = $this->max_dismissals;
722
		$obj->is_dismissible = $this->is_dismissible;
723
724
		if ( is_array( $this->redux_action ) ) {
725
			$obj->action = $this->redux_action;
726
		}
727
728
		return $obj;
729
	}
730
731
	/**
732
	 * Adds an item to the list that may be shown on the jitm
733
	 *
734
	 * @param string|callable $item item.
735
	 * @param string          $url url.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $url not be string|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...
736
	 *
737
	 * @return $this
738
	 */
739
	public function add_item_to_list( $item, $url = null ) {
740
		if ( ! isset( $this->content['list'] ) || ! is_array( $this->content['list'] ) ) {
741
			$this->content['list'] = array();
742
		}
743
744
		$this->content['list'][] = array(
745
			'item' => $item,
746
			'url'  => $url,
747
		);
748
749
		return $this;
750
	}
751
752
	/**
753
	 * Requires a widget to be active
754
	 *
755
	 * @param string $widget_slug The slug of the widget.
756
	 *
757
	 * @return $this
758
	 */
759
	public function has_widget_active( $widget_slug ) {
760
		$this->active_widgets[] = $widget_slug;
761
762
		return $this;
763
	}
764
765
	/**
766
	 * Requires a widget to be inactive
767
	 *
768
	 * @param string $widget_slug The slug of the widget.
769
	 *
770
	 * @return $this
771
	 */
772
	public function has_widget_inactive( $widget_slug ) {
773
		$this->inactive_widgets[] = $widget_slug;
774
775
		return $this;
776
	}
777
778
	/**
779
	 * Ensure that the browser carries a specific query string
780
	 *
781
	 * @param string $key The key to check for.
782
	 * @param string $value A regex to match.
783
	 *
784
	 * @return $this
785
	 */
786
	public function with_query_string( $key, $value ) {
787
		$this->query[ $key ] = $value;
788
789
		return $this;
790
	}
791
792
	/**
793
	 * A redux action dispatched when the CTA is clicked
794
	 *
795
	 * @param string $type Action name.
796
	 * @param object $props Action props.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $props not be object|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...
797
	 *
798
	 * @return $this
799
	 */
800
	public function with_redux_action( $type, $props = null ) {
801
		$this->redux_action = array_merge( array( 'type' => $type ), (array) $props );
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge(array('type'...$type), (array) $props) of type array is incompatible with the declared type string of property $redux_action.

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...
802
803
		return $this;
804
	}
805
806
	/**
807
	 * Ensure that a specific theme is active
808
	 *
809
	 * @param string $theme theme.
810
	 *
811
	 * @return $this
812
	 */
813
	public function with_active_theme( $theme ) {
814
		$this->theme = is_array( $theme ) ? $theme : array( $theme );
0 ignored issues
show
Documentation Bug introduced by
It seems like is_array($theme) ? $theme : array($theme) of type array is incompatible with the declared type string of property $theme.

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...
815
816
		return $this;
817
	}
818
819
	/**
820
	 * Only show this JITM when the specified plugin is inactive OR not installed
821
	 *
822
	 * @param string $plugin The path to the plugin.
823
	 *
824
	 * @return $this
825
	 */
826
	public function plugin_inactive( $plugin ) {
827
		$this->inactive_plugins[] = $plugin;
828
829
		return $this;
830
	}
831
832
	/**
833
	 * Only show this JITM when the specified plugin is active and installed
834
	 * Multiple calls are treated as OR: if _any_ of the plugins are active, the rule passes
835
	 *
836
	 * @param string $plugin The path to the plugin.
837
	 *
838
	 * @return $this
839
	 */
840
	public function plugin_active( $plugin ) {
841
		$this->active_plugins[] = $plugin;
842
843
		return $this;
844
	}
845
846
	/**
847
	 * A rule for a plugin being installed but either active or not active
848
	 *
849
	 * @param string $plugin The path to the plugin.
850
	 *
851
	 * @return $this
852
	 */
853
	public function plugin_installed( $plugin ) {
854
		$this->installed_plugins[] = $plugin;
855
856
		return $this;
857
	}
858
859
	/**
860
	 * A rule to check that a specific plugin is not installed
861
	 *
862
	 * @param string $plugin The path to the plugin.
863
	 *
864
	 * @return $this
865
	 */
866
	public function plugin_not_installed( $plugin ) {
867
		$this->uninstalled_plugins[] = $plugin;
868
869
		return $this;
870
	}
871
872
	/**
873
	 * Limits JITM to users who speak specific languages.
874
	 *
875
	 * @param array|string $lang target user locales.
876
	 */
877
	public function for_user_locale( $lang ) {
878
		if ( ! is_array( $lang ) ) {
879
			$lang = array( $lang );
880
		}
881
882
		$this->user_locales = array_map( 'strtolower', $lang );
883
884
		return $this;
885
	}
886
887
	/**
888
	 * Only show if the user is in the specified role
889
	 *
890
	 * @param string $role The role.
891
	 *
892
	 * @return $this
893
	 */
894
	public function user_is( $role ) {
895
		$this->roles[] = $role;
896
897
		return $this;
898
	}
899
900
	/**
901
	 * Show the specified message to the user
902
	 *
903
	 * @param string $message The message.
904
	 * @param string $description A longer description that shows up under the message.
905
	 * @param string $classes Any special classes to put on the card (such as is-upgrade-personal).
906
	 *
907
	 * @return $this
908
	 */
909
	public function show( $message, $description = '', $classes = '' ) {
910
		$this->content['message']     = $message;
911
		$this->content['description'] = $description;
912
		$this->content['classes']     = $classes;
913
914
		return $this;
915
	}
916
917
	/**
918
	 * Call a hook in the client to get the message to display
919
	 *
920
	 * @param string $hook The hook to call in the client.
921
	 *
922
	 * @return $this
923
	 */
924
	public function show_hook( $hook ) {
925
		$this->content['hook'] = $hook;
926
927
		return $this;
928
	}
929
930
	/**
931
	 * The message path that needs to match before showing
932
	 *
933
	 * Follows the form: wp:PAGE(REGEX):HOOK
934
	 *
935
	 * first part:
936
	 *
937
	 * wp: for wp-admin
938
	 *
939
	 * second part:
940
	 *
941
	 * a regex that will need to match "$screen->base"
942
	 *
943
	 * last part:
944
	 *
945
	 * The hook to display the content on, such as `admin-notices`
946
	 *
947
	 * @param string $regex The message path regex.
948
	 *
949
	 * @return $this
950
	 */
951
	public function message_path( $regex ) {
952
		$this->message_path_regex = $regex;
953
954
		return $this;
955
	}
956
957
	/**
958
	 * A call to action
959
	 *
960
	 * @param string $cta The message to display on the CTA button.
961
	 * @param string $hook A hook to call on the client side to filter the message with.
962
	 * @param string $link URL.
963
	 * @param bool   $primary Whether to use the primary button color or not.
964
	 *
965
	 * @return $this
966
	 */
967
	public function with_cta( $cta, $hook = '', $link = '', $primary = true ) {
968
		$this->cta['message'] = $cta;
969
		$this->cta['hook']    = $hook;
970
		$this->cta['link']    = $link;
971
		$this->cta['primary'] = $primary;
972
973
		return $this;
974
	}
975
976
	/**
977
	 * Adds an icon to the JITM
978
	 *
979
	 * @param string $emblem You may put an svg here, or a predifined emblem from Jetpack.
980
	 *
981
	 * @return $this
982
	 */
983
	public function with_icon( $emblem = 'jetpack' ) {
984
		$this->content['icon'] = $emblem;
985
986
		return $this;
987
	}
988
989
	/**
990
	 * Set the template of the JITM
991
	 *
992
	 * @param string $template Template name.
993
	 *
994
	 * @return $this
995
	 */
996
	public function with_template( $template ) {
997
		$this->template = $template;
998
999
		return $this;
1000
	}
1001
1002
	/**
1003
	 * Set's the priority of this specific jitm if there are any conflicts
1004
	 *
1005
	 * @param int $priority The priority. Higher numbers result in a higher priority.
1006
	 *
1007
	 * @return $this
1008
	 */
1009
	public function priority( $priority ) {
1010
		$this->priority = $priority;
1011
1012
		return $this;
1013
	}
1014
1015
	/**
1016
	 * Sets the amount of time to reshow a jitm after it has been dismissed
1017
	 *
1018
	 * @param int $seconds The number of seconds to show wait to show the jitm again.
1019
	 *
1020
	 * @return $this
1021
	 */
1022
	public function show_again_after( $seconds ) {
1023
		$this->next_show = $seconds;
1024
1025
		return $this;
1026
	}
1027
1028
	/**
1029
	 * Set the maximum number of dismissals before this jitm will never be shown again
1030
	 *
1031
	 * @param int $times The maximum number of times to show this message.
1032
	 *
1033
	 * @return $this
1034
	 */
1035
	public function max_dismissals( $times ) {
1036
		$this->max_dismissals = $times;
1037
1038
		return $this;
1039
	}
1040
1041
	/**
1042
	 * Sets a flag to check if a site is hosted with a certain partner.
1043
	 *
1044
	 * @param bool|string $partner_name partner name.
1045
	 *
1046
	 * @return $this
1047
	 */
1048
	public function is_hosted_with_partner( $partner_name ) {
1049
		$this->hosted_with_partner = $partner_name;
0 ignored issues
show
Documentation Bug introduced by
It seems like $partner_name can also be of type boolean. However, the property $hosted_with_partner is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1050
1051
		return $this;
1052
	}
1053
1054
	/**
1055
	 * Ensure that the blog has an option that satisfies the given matcher function.
1056
	 *
1057
	 * $matcher should return:
1058
	 *   false - to reject the value
1059
	 *   true  - to accept the value
1060
	 *   (int) - to accept the value and assign it a score (@see ->score_option_matches())
1061
	 *
1062
	 * @param string   $option_name option name.
1063
	 * @param callable $matcher - $matcher( $option_value ).
1064
	 *
1065
	 * @return $this
1066
	 */
1067
	public function with_option_matching( $option_name, callable $matcher ) {
1068
		$this->option_matches[ $option_name ] = $matcher( get_option( $option_name ) );
1069
1070
		return $this;
1071
	}
1072
1073
	/**
1074
	 * Limits the JITM to mobile or non-mobile browsers
1075
	 *
1076
	 * @param bool $mobile_browser - Whether to limit to mobile or non-mobile browsers.
1077
	 *
1078
	 * @return $this
1079
	 */
1080
	public function mobile_browser( $mobile_browser ) {
1081
		$this->mobile_browser = $mobile_browser;
1082
1083
		return $this;
1084
	}
1085
1086
	/**
1087
	 * Get the feature class name
1088
	 *
1089
	 * @return string
1090
	 */
1091
	public function get_feature_class() {
1092
		return $this->feature_class;
1093
	}
1094
1095
	/**
1096
	 * Whether or not to display the dismiss button for the JITM.
1097
	 *
1098
	 * @param bool $dismissible Should JITM be dismissible.
1099
	 *
1100
	 * @return $this
1101
	 */
1102
	public function is_dismissible( $dismissible ) {
1103
		$this->is_dismissible = $dismissible;
1104
1105
		return $this;
1106
	}
1107
1108
	/**
1109
	 * Replaces the CTA button link with an AJAX action trigger.
1110
	 *
1111
	 * @param string $action AJAX action name.
1112
	 *
1113
	 * @return $this
1114
	 */
1115
	public function with_cta_ajax_action( $action ) {
1116
		$this->cta['ajax_action'] = $action;
1117
1118
		return $this;
1119
	}
1120
1121
	/**
1122
	 * Get the site's dismissals
1123
	 *
1124
	 * @return array The array of dismissed jitms
1125
	 */
1126
	public function get_dismissals() {
1127
		return \Jetpack_Options::get_option( 'hide_jitm' ) ? \Jetpack_Options::get_option( 'hide_jitm' ) : array();
1128
	}
1129
1130
	/**
1131
	 * Get's the site's installed plugins
1132
	 *
1133
	 * @return array An array of installed plugins
1134
	 */
1135 View Code Duplication
	public function get_installed_plugins() {
1136
		if ( ! function_exists( 'get_plugins' ) ) {
1137
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
1138
		}
1139
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
1140
		$all_plugins = apply_filters( 'all_plugins', get_plugins() );
1141
1142
		return $all_plugins;
1143
	}
1144
1145
	/**
1146
	 * Get's the site's active plugins
1147
	 *
1148
	 * @return array An array of active plugins
1149
	 */
1150
	public function get_active_plugins() {
1151
		include_once ABSPATH . 'wp-admin/includes/plugin.php';
1152
		$active_plugins = \Jetpack::get_active_plugins();
1153
		if ( ! is_array( $active_plugins ) ) { // can be an empty string.
1154
			$active_plugins = array();
1155
		}
1156
1157
		return $active_plugins;
1158
	}
1159
1160
	/**
1161
	 * Get the list of widgets
1162
	 *
1163
	 * @return array
1164
	 */
1165
	public function get_widget_list() {
1166
		$list           = array();
1167
		$active_widgets = get_option( 'sidebars_widgets' );
1168
		foreach ( $active_widgets as $widgets ) {
1169
			if ( is_iterable( $widgets ) ) {
1170
				foreach ( $widgets as $widget ) {
1171
					$list[] = implode( '-', array_slice( explode( '-', $widget ), 0, - 1 ) );
1172
				}
1173
			} else {
1174
				$list[] = implode( '-', array_slice( explode( '-', $widgets ), 0, - 1 ) );
1175
			}
1176
		}
1177
1178
		return $list;
1179
	}
1180
1181
}
1182