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