PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / 7.8.9
Hustle – Email Marketing, Lead Generation, Optins, Popups v7.8.9
7.8.13 7.8.13.1 trunk 3.0 3.1 3.1.1 3.1.2 3.1.3 3.1.4 4.3.2 4.4.4 4.4.5 4.4.5.1 4.4.5.4 4.6 4.6.1.1 4.6.1.4 4.7.0.2 4.7.0.3 4.7.0.7 4.7.0.9 4.7.1.0 4.7.1.1 4.8.0.0 5.0.0 5.0.1 5.0.1.1 5.0.1.2 5.1 5.1.1 5.1.2 5.1.3 5.1.3.1 5.1.3.2 5.1.4 5.1.5 6.0 6.0.1 6.0.2 6.0.3 6.0.4.2 6.0.5 6.0.6.1 6.0.7 6.0.8.1 6.0.9 7.0.0.1 7.0.2 7.0.3 7.0.4 7.1.0 7.1.1 7.2.0 7.2.1 7.3.0 7.3.1 7.3.3 7.3.5 7.3.6 7.3.7 7.4.0 7.4.1 7.4.11 7.4.13 7.4.13.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.5.1 7.4.5.2 7.4.6 7.4.7 7.5.0 7.6.0 7.6.1 7.6.3 7.6.4 7.6.6 7.7.0 7.7.1 7.8.0 7.8.1 7.8.10 7.8.10.1 7.8.10.2 7.8.11 7.8.12 7.8.12.1 7.8.2 7.8.3 7.8.4 7.8.5 7.8.6 7.8.7 7.8.8 7.8.9 7.8.9.1 7.8.9.2 7.8.9.3
wordpress-popup / lib / free-dashboard / classes / class-handler.php
wordpress-popup / lib / free-dashboard / classes Last commit date
notices 9 months ago class-handler.php 9 months ago
class-handler.php
791 lines
1 <?php
2 /**
3 * Plugin notices handler.
4 *
5 * This class will take care of registering, queuing and showing different
6 * notices across WP pages.
7 *
8 * @since 2.0
9 * @author Incsub (Joel James)
10 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
11 * @copyright Copyright (c) 2022, Incsub
12 * @package WPMUDEV\Notices
13 * @subpackage Handler
14 */
15
16 namespace WPMUDEV\Notices;
17
18 // If this file is called directly, abort.
19 defined( 'WPINC' ) || die;
20
21 if ( ! class_exists( __NAMESPACE__ . '\\Handler' ) ) {
22 /**
23 * Class Module
24 *
25 * @since 2.0
26 * @package WPMUDEV\Notices
27 */
28 final class Handler {
29
30 /**
31 * Current version.
32 *
33 * @var string $version
34 *
35 * @since 2.0
36 */
37 public $version = '2.0.6';
38
39 /**
40 * Option name to store data.
41 *
42 * @var string $option_name
43 *
44 * @since 2.0
45 */
46 protected $option_name = 'wpmudev_notices';
47
48 /**
49 * Registered plugins for the opt-ins.
50 *
51 * @var array[] $plugins
52 *
53 * @since 2.0
54 */
55 private $plugins = array();
56
57 /**
58 * WordPress screen IDs to show notices on.
59 *
60 * @var array[] $screens
61 *
62 * @since 2.0
63 */
64 private $screens = array();
65
66 /**
67 * Disabled notice types.
68 *
69 * @var string[] $disabled
70 *
71 * @since 2.0
72 */
73 private $disabled = array( 'email', 'giveaway' );
74
75 /**
76 * Registered plugin notices data from db.
77 *
78 * @var array|null $queue
79 *
80 * @since 2.0
81 */
82 private $stored = null;
83
84 /**
85 * Notice types that are shown on WP Dashboard.
86 *
87 * @var array $wp_notices
88 *
89 * @since 2.0
90 */
91 private $wp_notices = array(
92 'email' => '\WPMUDEV\Notices\Notices\Email',
93 'rate' => '\WPMUDEV\Notices\Notices\Rating',
94 );
95
96 /**
97 * Notice type that are shown within plugin pages.
98 *
99 * @var array $plugin_notices
100 *
101 * @since 2.0
102 */
103 private $plugin_notices = array(
104 'giveaway' => '\WPMUDEV\Notices\Notices\Giveaway',
105 );
106
107 /**
108 * List of allowed actions.
109 *
110 * @var string[]
111 */
112 private $allowed_actions = array(
113 'dismiss_notice',
114 'extend_notice',
115 );
116
117 /**
118 * Construct handler class.
119 *
120 * @since 2.0
121 */
122 protected function __construct() {
123 // Register plugins.
124 add_action( 'wpmudev_register_notices', array( $this, 'register' ), 10, 2 );
125
126 // Always setup ajax actions.
127 add_action( 'wp_ajax_wpmudev_notices_action', array( $this, 'process_action' ) );
128
129 // Render admin notices.
130 add_action( 'load-index.php', array( $this, 'admin_notice' ) );
131 }
132
133 /**
134 * Initializes and returns the singleton instance.
135 *
136 * @since 2.0
137 *
138 * @return static
139 */
140 public static function instance() {
141 static $instance = null;
142
143 if ( null === $instance ) {
144 $instance = new self();
145 }
146
147 return $instance;
148 }
149
150 /**
151 * Register an active plugin for notices.
152 *
153 * ```php
154 * do_action(
155 * 'wpmudev_register_notices',
156 * 'beehive', // Plugin id.
157 * array(
158 * 'basename' => plugin_basename( BEEHIVE_PLUGIN_FILE ), // Required: Plugin basename (for backward compat).
159 * 'title' => 'Beehive', // Required: Plugin title.
160 * 'wp_slug' => 'beehive-analytics', // Required: wp.org slug of the plugin.
161 * 'cta_email' => __( 'Get Fast!', 'ga_trans' ), // Email button CTA.
162 * 'installed_on' => time(), // Optional: Plugin activated time.
163 * 'screens' => array( // Required: Plugin screen ids.
164 * 'toplevel_page_beehive',
165 * 'dashboard_page_beehive-accounts',
166 * 'dashboard_page_beehive-settings',
167 * 'dashboard_page_beehive-tutorials',
168 * 'dashboard_page_beehive-google-analytics',
169 * 'dashboard_page_beehive-google-tag-manager',
170 * ),
171 * )
172 * );
173 * ```
174 *
175 * @param string $plugin_id Plugin ID.
176 * @param array $options Options.
177 *
178 * @since 2.0
179 */
180 public function register( $plugin_id, array $options = array() ) {
181 // Plugin ID can't be empty.
182 if ( empty( $plugin_id ) ) {
183 return;
184 }
185
186 // Add to the plugins list.
187 $this->plugins[ $plugin_id ] = $options;
188
189 // Setup screens.
190 if ( ! empty( $options['screens'] ) ) {
191 $this->add_to_screens( $plugin_id, $options['screens'] );
192 }
193 }
194
195 /**
196 * Show an admin notice on WP page (not plugin's SUI pages).
197 *
198 * @since 2.0
199 *
200 * @return void
201 */
202 public function admin_notice() {
203 if ( is_super_admin() ) {
204 add_action( 'all_admin_notices', array( $this, 'render_admin_notice' ) );
205 }
206 }
207
208 /**
209 * Show a notice on current plugin page.
210 *
211 * @since 2.0
212 *
213 * @return void
214 */
215 public function plugin_notice() {
216 if ( is_super_admin() ) {
217 add_action( 'all_admin_notices', array( $this, 'render_plugin_notice' ) );
218 }
219 }
220
221 /**
222 * Render admin notice content.
223 *
224 * @since 2.0
225 *
226 * @return void
227 */
228 public function render_admin_notice() {
229 $this->render( false, array_keys( $this->wp_notices ) );
230 }
231
232 /**
233 * Render a plugin notice content.
234 *
235 * @since 2.0
236 *
237 * @return void
238 */
239 public function render_plugin_notice() {
240 // Get current screen.
241 $screen = get_current_screen();
242
243 // Continue only if registered screen.
244 if ( empty( $screen->id ) || empty( $this->screens[ $screen->id ] ) ) {
245 return;
246 }
247
248 $this->render(
249 $this->screens[ $screen->id ],
250 array_keys( $this->plugin_notices )
251 );
252 }
253
254 /**
255 * Process a notice action.
256 *
257 * All ajax requests from the notice are processed here.
258 * After nonce verification the action will be processed if a matching
259 * method is already defined.
260 *
261 * @since 2.0
262 */
263 public function process_action() {
264 // Check required fields.
265 if ( ! isset( $_POST['plugin_id'], $_POST['notice_action'], $_POST['notice_type'], $_POST['nonce'] ) ) {
266 wp_die( esc_html__( 'Required fields are missing.', 'wdev_frash' ) );
267 }
268
269 // Only admins can do this.
270 if ( ! is_super_admin() ) {
271 wp_die( esc_html__( 'Access check failed.', 'wdev_frash' ) );
272 }
273
274 // Get request data.
275 $plugin = sanitize_text_field( wp_unslash( $_POST['plugin_id'] ) );
276 $action = sanitize_text_field( wp_unslash( $_POST['notice_action'] ) );
277 $type = sanitize_text_field( wp_unslash( $_POST['notice_type'] ) );
278 $nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) );
279
280 // Verify nonce.
281 if ( ! wp_verify_nonce( $nonce, 'wpmudev_notices_action' ) ) {
282 wp_die( esc_html__( 'Nonce verification failed.', 'wdev_frash' ) );
283 }
284
285 // Check if action is valid.
286 if ( ! in_array( $action, $this->allowed_actions, true ) ) {
287 wp_die( esc_html__( 'Invalid action.', 'wdev_frash' ) );
288 }
289
290 // Initialize the options.
291 $this->init_option();
292
293 // Get the notice class.
294 $notice = $this->get_notice( $plugin, $type );
295
296 // Process action if defined on this class.
297 if ( method_exists( $this, $action ) ) {
298 call_user_func( array( $this, $action ), $plugin, $type );
299 } elseif ( is_object( $notice ) && method_exists( $notice, $action ) ) {
300 // Process action if defined on the notice class.
301 call_user_func( array( $notice, $action ), $plugin );
302 }
303
304 /**
305 * Action hook to do something after a notice action is performed.
306 *
307 * @param string $action Action.
308 * @param string $plugin Plugin ID.
309 * @param string $type Notice type.
310 *
311 * @since 2.0
312 */
313 do_action( 'wpmudev_notices_after_notice_action', $action, $plugin, $type );
314
315 wp_die();
316 }
317
318 /**
319 * Remove a notice from the queue.
320 *
321 * @param string $plugin Plugin ID.
322 * @param string $type Notice type.
323 *
324 * @since 2.0
325 *
326 * @return void
327 */
328 public function dismiss_notice( $plugin, $type ) {
329 // Remove from the queue.
330 if ( isset( $this->stored['queue'][ $plugin ][ $type ] ) ) {
331 unset( $this->stored['queue'][ $plugin ][ $type ] );
332 }
333
334 // Setup done list.
335 if ( ! isset( $this->stored['done'][ $plugin ] ) ) {
336 $this->stored['done'][ $plugin ] = array();
337 }
338
339 // Mark as done.
340 $this->stored['done'][ $plugin ][ $type ] = time();
341
342 // Update the queue.
343 $this->update_option();
344 }
345
346 /**
347 * Getter for the queue data.
348 *
349 * @since 2.0
350 *
351 * @return array
352 */
353 public function get_option() {
354 $this->init_option();
355
356 return $this->stored;
357 }
358
359 /**
360 * Update the notices stored data in db.
361 *
362 * @param array $data Option data (optional).
363 *
364 * @since 2.0
365 *
366 * @return bool
367 */
368 public function update_option( $data = false ) {
369 // If new data is provided use it.
370 if ( ! empty( $data ) ) {
371 $this->stored = $data;
372 }
373
374 // Update the data.
375 return update_site_option( $this->option_name, $this->stored );
376 }
377
378 /**
379 * Render notice for the current screen.
380 *
381 * @param string|false $plugin_id Plugin id (false to check all plugins).
382 * @param array $types Notice types to render.
383 *
384 * @since 2.0
385 *
386 * @return void|string
387 */
388 protected function render( $plugin_id = false, $types = array() ) {
389 // Setup queue when required.
390 $this->setup_queue();
391
392 if ( empty( $plugin_id ) ) {
393 // Get a random notice.
394 $notice = $this->get_random_notice( $types, $plugin_id );
395 } else {
396 // Get a plugin's notice.
397 $notice = $this->get_plugin_notice( $plugin_id, $types );
398 }
399
400 // Render if notice found.
401 if ( ! empty( $notice ) && method_exists( $notice, 'render' ) ) {
402 return call_user_func( array( $notice, 'render' ), $plugin_id );
403 }
404 }
405
406 /**
407 * Set screen IDs for the notices.
408 *
409 * NOTE: Only one plugin can use one screen id.
410 *
411 * @param string $plugin_id Plugin ID.
412 * @param array $screens Screen IDs.
413 *
414 * @since 2.0
415 *
416 * @return void
417 */
418 protected function add_to_screens( $plugin_id, array $screens ) {
419 // Set the screens.
420 if ( ! empty( $screens ) ) {
421 foreach ( $screens as $screen_id ) {
422 $this->screens[ $screen_id ] = $plugin_id;
423
424 // Remove network suffix for page hook.
425 if ( is_multisite() ) {
426 $screen_id = str_replace( '-network', '', $screen_id );
427 }
428
429 // Register screen notice.
430 add_action( "load-$screen_id", array( $this, 'plugin_notice' ) );
431 }
432 }
433 }
434
435 /**
436 * Setup the notices queue when ready.
437 *
438 * To avoid calling db queries we need to do this only before
439 * a notice is being rendered.
440 *
441 * @since 2.0
442 *
443 * @return void
444 */
445 protected function setup_queue() {
446 // Initialize data.
447 $this->init_option();
448
449 // Setup all registered plugins to in queue.
450 foreach ( $this->plugins as $plugin_id => $options ) {
451 $this->add_to_queue( $plugin_id, $options );
452 }
453 }
454
455 /**
456 * Set the queue for the plugin if required.
457 *
458 * We should always schedule all notice types even if they
459 * are disabled. Then only we can enable it later easily.
460 * Disabled notices won't be considered when taken from the queue.
461 *
462 * @param string $plugin_id Plugin ID.
463 * @param array $options Options.
464 *
465 * @since 2.0
466 *
467 * @return void
468 */
469 protected function add_to_queue( $plugin_id, array $options ) {
470 // Store to notice queue if not saved already.
471 if ( ! isset( $this->stored['plugins'][ $plugin_id ] ) ) {
472 // Register plugin.
473 $this->stored['plugins'][ $plugin_id ] = time();
474 $this->stored['queue'][ $plugin_id ] = array();
475
476 // Add notices to queue.
477 foreach ( $this->get_types() as $type => $class_name ) {
478 // Notice class.
479 $notice = $this->get_notice( $plugin_id, $type );
480 // Schedule notice.
481 if ( ! empty( $notice ) ) {
482 $this->stored['queue'][ $plugin_id ][ $type ] = $notice->get_next_schedule( $options['installed_on'] );
483 }
484 }
485
486 // Upgrade if required.
487 if ( ! empty( $options['basename'] ) ) {
488 $this->maybe_upgrade( $plugin_id, $options['basename'] );
489 }
490
491 // Update the stored data.
492 $this->update_option();
493 }
494 }
495
496 /**
497 * Init the notices stored data.
498 *
499 * Get from the db only if not already initialized.
500 *
501 * @since 2.0
502 */
503 protected function init_option() {
504 if ( null === $this->stored ) {
505 $queue = (array) get_site_option( $this->option_name, array() );
506
507 $this->stored = wp_parse_args(
508 $queue,
509 array(
510 'plugins' => array(),
511 'queue' => array(),
512 'done' => array(),
513 )
514 );
515 }
516 }
517
518 /**
519 * Get a notice object from the entire due list.
520 *
521 * This is usually used for a common WP page where all plugins'
522 * notices are shown. Eg: WP Dashboard page.
523 *
524 * @param array $types Notice types.
525 * @param string|bool $plugin_id Plugin ID.
526 *
527 * @since 2.0
528 *
529 * @return object|false
530 */
531 protected function get_random_notice( array $types = array(), &$plugin_id = false ) {
532 if ( ! empty( $this->stored['queue'] ) ) {
533 // Check all due items.
534 foreach ( $this->stored['queue'] as $plugin => $notices ) {
535 if ( ! empty( $notices ) ) {
536 // Chose one with priority.
537 $notice = $this->choose_notice( $plugin, $notices, $types );
538 // Return only if a valid notice is selected.
539 if ( ! empty( $notice ) ) {
540 // Set the plugin id.
541 $plugin_id = $plugin;
542
543 return $notice;
544 }
545 }
546 }
547 }
548
549 return false;
550 }
551
552 /**
553 * Get a notice object for the plugin.
554 *
555 * Select one with priority from the due list for the plugin.
556 *
557 * @param string $plugin_id Plugin ID.
558 * @param array $types Notice types.
559 *
560 * @since 2.0
561 *
562 * @return object|false
563 */
564 protected function get_plugin_notice( $plugin_id, array $types = array() ) {
565 // Choose one notice from the due list.
566 if ( ! empty( $this->stored['queue'][ $plugin_id ] ) ) {
567 return $this->choose_notice(
568 $plugin_id,
569 $this->stored['queue'][ $plugin_id ],
570 $types
571 );
572 }
573
574 return false;
575 }
576
577 /**
578 * Choose a notice from the due list.
579 *
580 * Notice will be selected based on the order it's defined
581 * in the $types property of this class.
582 *
583 * @param string $plugin_id Plugin ID.
584 * @param array $notices Notices array.
585 * @param array $types Notice types.
586 *
587 * @since 2.0
588 *
589 * @return object|false
590 */
591 protected function choose_notice( $plugin_id, array $notices, array $types = array() ) {
592 foreach ( $this->get_types() as $type => $class ) {
593 // Not in the list.
594 if ( ! isset( $notices[ $type ] ) ) {
595 continue;
596 }
597
598 // Not a desired type, skip.
599 if ( ! empty( $types ) && ! in_array( $type, $types, true ) ) {
600 continue;
601 }
602
603 // Disabled type, skip.
604 if ( $this->is_disabled( $type, $plugin_id ) ) {
605 continue;
606 }
607
608 // Due time reached or passed.
609 if ( $this->get_current_time() >= (int) $notices[ $type ] ) {
610 // Get the notice class instance.
611 $notice = $this->get_notice( $plugin_id, $type );
612
613 // Return the notice object.
614 if ( ! empty( $notice ) && $notice->can_show( $plugin_id ) ) {
615 return $notice;
616 }
617 }
618 }
619
620 return false;
621 }
622
623 /**
624 * Get the notice type class instance.
625 *
626 * @param string $plugin_id Plugin ID.
627 * @param string $type Notice type.
628 *
629 * @since 2.0
630 *
631 * @return bool|object
632 */
633 protected function get_notice( $plugin_id, $type ) {
634 $types = $this->get_types();
635
636 // If a valid class found for the type.
637 if (
638 isset( $types[ $type ] )
639 && isset( $this->plugins[ $plugin_id ] )
640 && class_exists( $types[ $type ] )
641 ) {
642 /**
643 * Notice class name.
644 *
645 * @var Notices\Notice $class_name
646 */
647 $class_name = $types[ $type ];
648
649 return $class_name::instance( $this->plugins[ $plugin_id ] );
650 }
651
652 return false;
653 }
654
655 /**
656 * Get available notice types and classes.
657 *
658 * @since 2.0
659 *
660 * @return array
661 */
662 protected function get_types() {
663 return array_merge(
664 $this->wp_notices,
665 $this->plugin_notices
666 );
667 }
668
669 /**
670 * Get current time to use for due date check.
671 *
672 * This is used to enable custom time fot testing.
673 *
674 * @since 2.0
675 *
676 * @return int
677 */
678 protected function get_current_time() {
679 // Get custom time.
680 $time = filter_input( INPUT_GET, 'wpmudev_notice_time', FILTER_SANITIZE_SPECIAL_CHARS );
681
682 return empty( $time ) ? time() : (int) $time;
683 }
684
685 /**
686 * Check if a notice type is disabled.
687 *
688 * @since 2.0.3
689 *
690 * @param string $type Notice type.
691 * @param string $plugin Plugin ID.
692 *
693 * @return bool
694 */
695 protected function is_disabled( $type, $plugin ) {
696 /**
697 * Filter to modify disabled notices list.
698 *
699 * @param array $disabled Disabled list.
700 * @param string $plugin Plugin ID.
701 */
702 $disabled = apply_filters( 'wpmudev_notices_disabled_notices', $this->disabled, $plugin );
703
704 // Check if notice type is disabled.
705 $is_disabled = in_array( $type, $disabled, true );
706
707 /**
708 * Filter to enable/disable a notice type.
709 *
710 * @param bool $is_disabled Is disabled.
711 * @param string $type Notice type.
712 * @param string $plugin Plugin ID.
713 */
714 return apply_filters( 'wpmudev_notices_is_disabled', $is_disabled, $type, $plugin );
715 }
716
717 /**
718 * Optional upgrade from old version (WDEV Frash).
719 *
720 * If old data exist, we need to use it before registering new time.
721 * Used only when registering for the first time.
722 *
723 * @param string $plugin_id Plugin ID.
724 * @param string $basename Plugin basename (used in old plugins).
725 *
726 * @since 2.0
727 * @deprecated 2.0 We may remove this in future.
728 *
729 * @return void
730 */
731 protected function maybe_upgrade( $plugin_id, $basename ) {
732 // Old settings data.
733 $deprecated = get_site_option( 'wdev-frash' );
734
735 // Old notice exists, upgrade it.
736 if ( ! empty( $deprecated ) ) {
737 $deprecated = wp_parse_args(
738 (array) $deprecated,
739 array(
740 'plugins' => array(),
741 'queue' => array(),
742 'done' => array(),
743 )
744 );
745
746 // Not found in old settings.
747 if ( ! isset( $deprecated['plugins'][ $basename ] ) ) {
748 return;
749 }
750
751 // Use old registered time.
752 $this->stored['plugins'][ $plugin_id ] = $deprecated['plugins'][ $basename ];
753 // Existing plugin, so show giveaway right away.
754 $this->stored['queue'][ $plugin_id ]['giveaway'] = time();
755
756 // Only email and rate types.
757 foreach ( array( 'email', 'rate' ) as $type ) {
758 // Old key hash.
759 $hash = md5( $basename . '-' . $type );
760
761 if ( isset( $deprecated['queue'][ $hash ]['show_at'] ) ) {
762 // Use the existing time.
763 $this->stored['queue'][ $plugin_id ][ $type ] = $deprecated['queue'][ $hash ]['show_at'];
764 // Remove from old settings.
765 unset( $deprecated['queue'][ $hash ] );
766 } else {
767 // Check if notice type found in dismissed list.
768 $dismissed = array_filter(
769 $deprecated['done'],
770 function ( $item ) use ( $basename, $type ) {
771 return $item['plugin'] === $basename && $item['type'] === $type;
772 }
773 );
774
775 // Already shown and dismissed, remove it.
776 if ( ! empty( $dismissed[0]['handled_at'] ) ) {
777 // Remove from queue.
778 unset( $this->stored['queue'][ $plugin_id ][ $type ] );
779 // Move to done list.
780 $this->stored['done'][ $plugin_id ][ $type ] = $dismissed[0]['handled_at'];
781 }
782 }
783 }
784
785 // Do not delete it yet for backward compatibility.
786 update_site_option( 'wdev-frash', $deprecated );
787 }
788 }
789 }
790 }
791