PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 1.4.4
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v1.4.4
3.6.0 3.5.0 trunk 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.6.1 1.0.7 1.0.8 1.0.9 1.1 1.1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 All 110 releases
buttonizer-multifunctional-button / freemius / includes / managers / class-fs-admin-menu-manager.php

class-fs-admin-menu-manager.php in Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder 1.4.4, at freemius/includes/managers/class-fs-admin-menu-manager.php

850 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Freemius
4 * @copyright Copyright (c) 2015, Freemius, Inc.
5 * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6 * @since 1.1.3
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 class FS_Admin_Menu_Manager {
14
15 #region Properties
16
17 /**
18 * @since 1.2.2
19 *
20 * @var string
21 */
22 protected $_module_unique_affix;
23
24 /**
25 * @since 1.2.2
26 *
27 * @var number
28 */
29 protected $_module_id;
30
31 /**
32 * @since 1.2.2
33 *
34 * @var string
35 */
36 protected $_module_type;
37
38 /**
39 * @since 1.0.6
40 *
41 * @var string
42 */
43 private $_menu_slug;
44 /**
45 * @since 1.1.3
46 *
47 * @var string
48 */
49 private $_parent_slug;
50 /**
51 * @since 1.1.3
52 *
53 * @var string
54 */
55 private $_parent_type;
56 /**
57 * @since 1.1.3
58 *
59 * @var string
60 */
61 private $_type;
62 /**
63 * @since 1.1.3
64 *
65 * @var bool
66 */
67 private $_is_top_level;
68 /**
69 * @since 1.1.3
70 *
71 * @var bool
72 */
73 private $_is_override_exact;
74 /**
75 * @since 1.1.3
76 *
77 * @var array<string,bool>
78 */
79 private $_default_submenu_items;
80 /**
81 * @since 1.1.3
82 *
83 * @var string
84 */
85 private $_first_time_path;
86 /**
87 * @since 1.2.2
88 *
89 * @var bool
90 */
91 private $_menu_exists;
92
93 #endregion Properties
94
95 /**
96 * @var FS_Logger
97 */
98 protected $_logger;
99
100 #region Singleton
101
102 /**
103 * @var FS_Admin_Menu_Manager[]
104 */
105 private static $_instances = array();
106
107 /**
108 * @param number $module_id
109 * @param string $module_type
110 * @param string $module_unique_affix
111 *
112 * @return FS_Admin_Menu_Manager
113 */
114 static function instance( $module_id, $module_type, $module_unique_affix ) {
115 $key = 'm_' . $module_id;
116
117 if ( ! isset( self::$_instances[ $key ] ) ) {
118 self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix );
119 }
120
121 return self::$_instances[ $key ];
122 }
123
124 protected function __construct( $module_id, $module_type, $module_unique_affix ) {
125 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
126
127 $this->_module_id = $module_id;
128 $this->_module_type = $module_type;
129 $this->_module_unique_affix = $module_unique_affix;
130 }
131
132 #endregion Singleton
133
134 #region Helpers
135
136 private function get_option( &$options, $key, $default = false ) {
137 return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
138 }
139
140 private function get_bool_option( &$options, $key, $default = false ) {
141 return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
142 }
143
144 #endregion Helpers
145
146 /**
147 * @param array $menu
148 * @param bool $is_addon
149 */
150 function init( $menu, $is_addon = false ) {
151 $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) );
152
153 $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix );
154
155 $this->_default_submenu_items = array();
156 // @deprecated
157 $this->_type = 'page';
158 $this->_is_top_level = true;
159 $this->_is_override_exact = false;
160 $this->_parent_slug = false;
161 // @deprecated
162 $this->_parent_type = 'page';
163
164 if ( ! $is_addon && isset( $menu ) ) {
165 $this->_default_submenu_items = array(
166 'contact' => $this->get_bool_option( $menu, 'contact', true ),
167 'support' => $this->get_bool_option( $menu, 'support', true ),
168 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ),
169 'account' => $this->get_bool_option( $menu, 'account', true ),
170 'pricing' => $this->get_bool_option( $menu, 'pricing', true ),
171 'addons' => $this->get_bool_option( $menu, 'addons', true ),
172 );
173
174 // @deprecated
175 $this->_type = $this->get_option( $menu, 'type', 'page' );
176 $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' );
177
178 if ( isset( $menu['parent'] ) ) {
179 $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' );
180 // @deprecated
181 $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' );
182
183 // If parent's slug is different, then it's NOT a top level menu item.
184 $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug );
185 } else {
186 /**
187 * If no parent then top level if:
188 * - Has custom admin menu ('page')
189 * - CPT menu type ('cpt')
190 */
191 // $this->_is_top_level = in_array( $this->_type, array(
192 // 'cpt',
193 // 'page'
194 // ) );
195 }
196
197 $this->_first_time_path = $this->get_option( $menu, 'first-path', false );
198 if ( ! empty( $this->_first_time_path ) && is_string( $this->_first_time_path ) ) {
199 $this->_first_time_path = admin_url( $this->_first_time_path, 'admin' );
200 }
201 }
202 }
203
204 /**
205 * Check if top level menu.
206 *
207 * @author Vova Feldman (@svovaf)
208 * @since 1.1.3
209 *
210 * @return bool False if submenu item.
211 */
212 function is_top_level() {
213 return $this->_is_top_level;
214 }
215
216 /**
217 * Check if the page should be override on exact URL match.
218 *
219 * @author Vova Feldman (@svovaf)
220 * @since 1.1.3
221 *
222 * @return bool False if submenu item.
223 */
224 function is_override_exact() {
225 return $this->_is_override_exact;
226 }
227
228
229 /**
230 * Get the path of the page the user should be forwarded to after first activation.
231 *
232 * @author Vova Feldman (@svovaf)
233 * @since 1.1.3
234 *
235 * @return string
236 */
237 function get_first_time_path() {
238 return $this->_first_time_path;
239 }
240
241 /**
242 * Check if plugin's menu item is part of a custom top level menu.
243 *
244 * @author Vova Feldman (@svovaf)
245 * @since 1.1.3
246 *
247 * @return bool
248 */
249 function has_custom_parent() {
250 return ! $this->_is_top_level && is_string( $this->_parent_slug );
251 }
252
253 /**
254 * @author Leo Fajardo (@leorw)
255 * @since 1.2.2
256 *
257 * @return bool
258 */
259 function has_menu() {
260 return $this->_menu_exists;
261 }
262
263 /**
264 * @author Vova Feldman (@svovaf)
265 * @since 1.1.3
266 *
267 * @param string $id
268 * @param bool $default
269 * @param bool $ignore_menu_existence Since 1.2.2.7 If true, check if the submenu item visible even if there's no parent menu.
270 *
271 * @return bool
272 */
273 function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) {
274 if ( ! $ignore_menu_existence && ! $this->has_menu() ) {
275 return false;
276 }
277
278 return fs_apply_filter(
279 $this->_module_unique_affix,
280 'is_submenu_visible',
281 $this->get_bool_option( $this->_default_submenu_items, $id, $default ),
282 $id
283 );
284 }
285
286 /**
287 * Calculates admin settings menu slug.
288 * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug.
289 *
290 * @author Vova Feldman (@svovaf)
291 * @since 1.1.3
292 *
293 * @param string $page
294 *
295 * @return string
296 */
297 function get_slug( $page = '' ) {
298 return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ?
299 $this->_menu_slug :
300 $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) );
301 }
302
303 /**
304 * @author Vova Feldman (@svovaf)
305 * @since 1.1.3
306 *
307 * @return string
308 */
309 function get_parent_slug() {
310 return $this->_parent_slug;
311 }
312
313 /**
314 * @author Vova Feldman (@svovaf)
315 * @since 1.1.3
316 *
317 * @return string
318 */
319 function get_type() {
320 return $this->_type;
321 }
322
323 /**
324 * @author Vova Feldman (@svovaf)
325 * @since 1.1.3
326 *
327 * @return bool
328 */
329 function is_cpt() {
330 return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) ||
331 // Back compatibility.
332 'cpt' === $this->_type
333 );
334 }
335
336 /**
337 * @author Vova Feldman (@svovaf)
338 * @since 1.1.3
339 *
340 * @return string
341 */
342 function get_parent_type() {
343 return $this->_parent_type;
344 }
345
346 /**
347 * @author Vova Feldman (@svovaf)
348 * @since 1.1.3
349 *
350 * @return string
351 */
352 function get_raw_slug() {
353 return $this->_menu_slug;
354 }
355
356 /**
357 * Get plugin's original menu slug.
358 *
359 * @author Vova Feldman (@svovaf)
360 * @since 1.1.3
361 *
362 * @return string
363 */
364 function get_original_menu_slug() {
365 if ( 'cpt' === $this->_type ) {
366 return add_query_arg( array(
367 'post_type' => $this->_menu_slug
368 ), 'edit.php' );
369 }
370
371 if ( false === strpos( $this->_menu_slug, '.php?' ) ) {
372 return $this->_menu_slug;
373 } else {
374 return $this->_module_unique_affix;
375 }
376 }
377
378 /**
379 * @author Vova Feldman (@svovaf)
380 * @since 1.1.3
381 *
382 * @return string
383 */
384 function get_top_level_menu_slug() {
385 return $this->has_custom_parent() ?
386 $this->get_parent_slug() :
387 $this->get_raw_slug();
388 }
389
390 /**
391 * Is user on plugin's admin activation page.
392 *
393 * @author Vova Feldman (@svovaf)
394 * @since 1.0.8
395 *
396 * @return bool
397 */
398 function is_main_settings_page() {
399 if ( $this->_menu_exists &&
400 ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) )
401 ) {
402 /**
403 * Module has a settings menu and the context page is the main settings page, so assume it's in
404 * activation (doesn't really check if already opted-in/skipped or not).
405 *
406 * @since 1.2.2
407 */
408 return true;
409 }
410
411 global $pagenow;
412 if ( ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && Freemius::is_themes_page() ) {
413 /**
414 * In activation only when show_optin query string param is given.
415 *
416 * @since 1.2.2
417 */
418 return fs_request_get_bool( $this->_module_unique_affix . '_show_optin' );
419 }
420
421 return false;
422 }
423
424 #region Submenu Override
425
426 /**
427 * Override submenu's action.
428 *
429 * @author Vova Feldman (@svovaf)
430 * @since 1.1.0
431 *
432 * @param string $parent_slug
433 * @param string $menu_slug
434 * @param callable $function
435 *
436 * @return false|string If submenu exist, will return the hook name.
437 */
438 function override_submenu_action( $parent_slug, $menu_slug, $function ) {
439 global $submenu;
440
441 $menu_slug = plugin_basename( $menu_slug );
442 $parent_slug = plugin_basename( $parent_slug );
443
444 if ( ! isset( $submenu[ $parent_slug ] ) ) {
445 // Parent menu not exist.
446 return false;
447 }
448
449 $found_submenu_item = false;
450 foreach ( $submenu[ $parent_slug ] as $submenu_item ) {
451 if ( $menu_slug === $submenu_item[2] ) {
452 $found_submenu_item = $submenu_item;
453 break;
454 }
455 }
456
457 if ( false === $found_submenu_item ) {
458 // Submenu item not found.
459 return false;
460 }
461
462 // Remove current function.
463 $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug );
464 remove_all_actions( $hookname );
465
466 // Attach new action.
467 add_action( $hookname, $function );
468
469 return $hookname;
470 }
471
472 #endregion Submenu Override
473
474 #region Top level menu Override
475
476 /**
477 * Find plugin's admin dashboard main menu item.
478 *
479 * @author Vova Feldman (@svovaf)
480 * @since 1.0.2
481 *
482 * @return string[]|false
483 */
484 private function find_top_level_menu() {
485 global $menu;
486
487 $position = - 1;
488 $found_menu = false;
489
490 $menu_slug = $this->get_raw_slug();
491
492 $hook_name = get_plugin_page_hookname( $menu_slug, '' );
493 foreach ( $menu as $pos => $m ) {
494 if ( $menu_slug === $m[2] ) {
495 $position = $pos;
496 $found_menu = $m;
497 break;
498 }
499 }
500
501 if ( false === $found_menu ) {
502 return false;
503 }
504
505 return array(
506 'menu' => $found_menu,
507 'position' => $position,
508 'hook_name' => $hook_name
509 );
510 }
511
512 /**
513 * Find plugin's admin dashboard main submenu item.
514 *
515 * @author Vova Feldman (@svovaf)
516 * @since 1.2.1.6
517 *
518 * @return array|false
519 */
520 private function find_main_submenu() {
521 global $submenu;
522
523 $top_level_menu_slug = $this->get_top_level_menu_slug();
524
525 if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) {
526 return false;
527 }
528
529 $submenu_slug = $this->get_raw_slug();
530
531 $position = - 1;
532 $found_submenu = false;
533
534 $hook_name = get_plugin_page_hookname( $submenu_slug, '' );
535
536 foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) {
537 if ( $submenu_slug === $sub[2] ) {
538 $position = $pos;
539 $found_submenu = $sub;
540 }
541 }
542
543 if ( false === $found_submenu ) {
544 return false;
545 }
546
547 return array(
548 'menu' => $found_submenu,
549 'parent_slug' => $top_level_menu_slug,
550 'position' => $position,
551 'hook_name' => $hook_name
552 );
553 }
554
555 /**
556 * Remove all sub-menu items.
557 *
558 * @author Vova Feldman (@svovaf)
559 * @since 1.0.7
560 *
561 * @return bool If submenu with plugin's menu slug was found.
562 */
563 private function remove_all_submenu_items() {
564 global $submenu;
565
566 $menu_slug = $this->get_raw_slug();
567
568 if ( ! isset( $submenu[ $menu_slug ] ) ) {
569 return false;
570 }
571
572 /**
573 * This method is NOT executed for WordPress.org themes.
574 * Since we maintain only one version of the SDK we added this small
575 * hack to avoid the error from Theme Check since it's a false-positive.
576 *
577 * @author Vova Feldman (@svovaf)
578 * @since 1.2.2.7
579 */
580 $submenu_ref = &$submenu;
581 $submenu_ref[ $menu_slug ] = array();
582
583 return true;
584 }
585
586 /**
587 *
588 * @author Vova Feldman (@svovaf)
589 * @since 1.0.9
590 *
591 * @return false|array[string]mixed
592 */
593 function remove_menu_item() {
594 $this->_logger->entrance();
595
596 // Find main menu item.
597 $menu = $this->find_top_level_menu();
598
599 if ( false === $menu ) {
600 return false;
601 }
602
603 // Remove it with its actions.
604 remove_all_actions( $menu['hook_name'] );
605
606 // Remove all submenu items.
607 $this->remove_all_submenu_items();
608
609 return $menu;
610 }
611
612 /**
613 * Get module's main admin setting page URL.
614 *
615 * @todo This method was only tested for wp.org compliant themes with a submenu item. Need to test for plugins with top level, submenu, and CPT top level, menu items.
616 *
617 * @author Vova Feldman (@svovaf)
618 * @since 1.2.2.7
619 *
620 * @return string
621 */
622 function main_menu_url() {
623 $this->_logger->entrance();
624
625 if ( $this->_is_top_level ) {
626 $menu = $this->find_top_level_menu();
627 } else {
628 $menu = $this->find_main_submenu();
629 }
630
631 $parent_slug = isset( $menu['parent_slug'] ) ?
632 $menu['parent_slug'] :
633 'admin.php';
634
635 return admin_url( $parent_slug . '?page=' . $menu['menu'][2] );
636 }
637
638 /**
639 * @author Vova Feldman (@svovaf)
640 * @since 1.1.4
641 *
642 * @param callable $function
643 *
644 * @return false|array[string]mixed
645 */
646 function override_menu_item( $function ) {
647 $found_menu = $this->remove_menu_item();
648
649 if ( false === $found_menu ) {
650 return false;
651 }
652
653 if ( ! $this->is_top_level() || ! $this->is_cpt() ) {
654 $menu_slug = plugin_basename( $this->get_slug() );
655
656 $hookname = get_plugin_page_hookname( $menu_slug, '' );
657
658 // Override menu action.
659 add_action( $hookname, $function );
660 } else {
661 global $menu;
662
663 // Remove original CPT menu.
664 unset( $menu[ $found_menu['position'] ] );
665
666 // Create new top-level menu action.
667 $hookname = self::add_page(
668 $found_menu['menu'][3],
669 $found_menu['menu'][0],
670 'manage_options',
671 $this->get_slug(),
672 $function,
673 $found_menu['menu'][6],
674 $found_menu['position']
675 );
676 }
677
678 return $hookname;
679 }
680
681 /**
682 * Adds a counter to the module's top level menu item.
683 *
684 * @author Vova Feldman (@svovaf)
685 * @since 1.2.1.5
686 *
687 * @param int $counter
688 * @param string $class
689 */
690 function add_counter_to_menu_item( $counter = 1, $class = '' ) {
691 global $menu, $submenu;
692
693 $mask = '%s <span class="update-plugins %s count-%3$s" aria-hidden="true"><span>%3$s<span class="screen-reader-text">%3$s notifications</span></span></span>';
694
695 /**
696 * This method is NOT executed for WordPress.org themes.
697 * Since we maintain only one version of the SDK we added this small
698 * hack to avoid the error from Theme Check since it's a false-positive.
699 *
700 * @author Vova Feldman (@svovaf)
701 * @since 1.2.2.7
702 */
703 $menu_ref = &$menu;
704 $submenu_ref = &$submenu;
705
706 if ( $this->_is_top_level ) {
707 // Find main menu item.
708 $found_menu = $this->find_top_level_menu();
709
710 if ( false !== $found_menu ) {
711 // Override menu label.
712 $menu_ref[ $found_menu['position'] ][0] = sprintf(
713 $mask,
714 $found_menu['menu'][0],
715 $class,
716 $counter
717 );
718 }
719 } else {
720 $found_submenu = $this->find_main_submenu();
721
722 if ( false !== $found_submenu ) {
723 // Override menu label.
724 $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf(
725 $mask,
726 $found_submenu['menu'][0],
727 $class,
728 $counter
729 );
730 }
731 }
732 }
733
734 #endregion Top level menu Override
735
736 /**
737 * Add a top-level menu page.
738 *
739 * Note for WordPress.org Theme/Plugin reviewer:
740 *
741 * This is a replication of `add_menu_page()` to avoid Theme Check warning.
742 *
743 * Why?
744 * ====
745 * Freemius is an SDK for plugin and theme developers. Since the core
746 * of the SDK is relevant both for plugins and themes, for obvious reasons,
747 * we only develop and maintain one code base.
748 *
749 * This method will not run for wp.org themes (only plugins) since theme
750 * admin settings/options are now only allowed in the customizer.
751 *
752 * If you have any questions or need clarifications, please don't hesitate
753 * pinging me on slack, my username is @svovaf.
754 *
755 * @author Vova Feldman (@svovaf)
756 * @since 1.2.2
757 *
758 * @param string $page_title The text to be displayed in the title tags of the page when the menu is
759 * selected.
760 * @param string $menu_title The text to be used for the menu.
761 * @param string $capability The capability required for this menu to be displayed to the user.
762 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
763 * @param callable|string $function The function to be called to output the content for this page.
764 * @param string $icon_url The URL to the icon to be used for this menu.
765 * * Pass a base64-encoded SVG using a data URI, which will be colored to
766 * match the color scheme. This should begin with
767 * 'data:image/svg+xml;base64,'.
768 * * Pass the name of a Dashicons helper class to use a font icon,
769 * e.g. 'dashicons-chart-pie'.
770 * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added
771 * via CSS.
772 * @param int $position The position in the menu order this one should appear.
773 *
774 * @return string The resulting page's hook_suffix.
775 */
776 static function add_page(
777 $page_title,
778 $menu_title,
779 $capability,
780 $menu_slug,
781 $function = '',
782 $icon_url = '',
783 $position = null
784 ) {
785 $fn = 'add_menu' . '_page';
786
787 return $fn(
788 $page_title,
789 $menu_title,
790 $capability,
791 $menu_slug,
792 $function,
793 $icon_url,
794 $position
795 );
796 }
797
798 /**
799 * Add a submenu page.
800 *
801 * Note for WordPress.org Theme/Plugin reviewer:
802 *
803 * This is a replication of `add_submenu_page()` to avoid Theme Check warning.
804 *
805 * Why?
806 * ====
807 * Freemius is an SDK for plugin and theme developers. Since the core
808 * of the SDK is relevant both for plugins and themes, for obvious reasons,
809 * we only develop and maintain one code base.
810 *
811 * This method will not run for wp.org themes (only plugins) since theme
812 * admin settings/options are now only allowed in the customizer.
813 *
814 * If you have any questions or need clarifications, please don't hesitate
815 * pinging me on slack, my username is @svovaf.
816 *
817 * @author Vova Feldman (@svovaf)
818 * @since 1.2.2
819 *
820 * @param string $parent_slug The slug name for the parent menu (or the file name of a standard
821 * WordPress admin page).
822 * @param string $page_title The text to be displayed in the title tags of the page when the menu is
823 * selected.
824 * @param string $menu_title The text to be used for the menu.
825 * @param string $capability The capability required for this menu to be displayed to the user.
826 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
827 * @param callable|string $function The function to be called to output the content for this page.
828 *
829 * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability
830 * required.
831 */
832 static function add_subpage(
833 $parent_slug,
834 $page_title,
835 $menu_title,
836 $capability,
837 $menu_slug,
838 $function = ''
839 ) {
840 $fn = 'add_submenu' . '_page';
841
842 return $fn( $parent_slug,
843 $page_title,
844 $menu_title,
845 $capability,
846 $menu_slug,
847 $function
848 );
849 }
850 }