PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 1.0.5
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v1.0.5
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.0.5, at freemius/includes/managers/class-fs-admin-menu-manager.php

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