PluginProbe
Passster – Password Protect Pages and Content / 4.3.17
Passster – Password Protect Pages and Content v4.3.17
4.3.17 4.3.16 4.3.15 4.3.14 4.3.12 4.3.13 4.3.11 4.3.10 4.3.9 4.3.8 4.3.7 4.3.6 4.3.5 trunk 3.5.4 3.5.5.2 3.5.5.8 3.5.5.9 4.0 4.1.4 4.2.10 4.2.11 4.2.12 4.2.13 4.2.14 All 49 releases
← All changes | inc/freemius/includes/managers/class-fs-admin-menu-manager.php +1025 -1005 4.2.13 → 4.3.17 View file →
@@ -1,1006 +1,1026 @@
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 - * @since 2.0.0
94 - *
95 - * @var bool
96 - */
97 - private $_network_menu_exists;
98 -
99 - #endregion Properties
100 -
101 - /**
102 - * @var FS_Logger
103 - */
104 - protected $_logger;
105 -
106 - #region Singleton
107 -
108 - /**
109 - * @var FS_Admin_Menu_Manager[]
110 - */
111 - private static $_instances = array();
112 -
113 - /**
114 - * @param number $module_id
115 - * @param string $module_type
116 - * @param string $module_unique_affix
117 - *
118 - * @return FS_Admin_Menu_Manager
119 - */
120 - static function instance( $module_id, $module_type, $module_unique_affix ) {
121 - $key = 'm_' . $module_id;
122 -
123 - if ( ! isset( self::$_instances[ $key ] ) ) {
124 - self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix );
125 - }
126 -
127 - return self::$_instances[ $key ];
128 - }
129 -
130 - protected function __construct( $module_id, $module_type, $module_unique_affix ) {
131 - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
132 -
133 - $this->_module_id = $module_id;
134 - $this->_module_type = $module_type;
135 - $this->_module_unique_affix = $module_unique_affix;
136 - }
137 -
138 - #endregion Singleton
139 -
140 - #region Helpers
141 -
142 - private function get_option( &$options, $key, $default = false ) {
143 - return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
144 - }
145 -
146 - private function get_bool_option( &$options, $key, $default = false ) {
147 - return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
148 - }
149 -
150 - #endregion Helpers
151 -
152 - /**
153 - * @param array $menu
154 - * @param bool $is_addon
155 - */
156 - function init( $menu, $is_addon = false ) {
157 - $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) );
158 - $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] );
159 -
160 - $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix );
161 -
162 - $this->_default_submenu_items = array();
163 - // @deprecated
164 - $this->_type = 'page';
165 - $this->_is_top_level = true;
166 - $this->_is_override_exact = false;
167 - $this->_parent_slug = false;
168 - // @deprecated
169 - $this->_parent_type = 'page';
170 -
171 - if ( isset( $menu ) ) {
172 - if ( ! $is_addon ) {
173 - $this->_default_submenu_items = array(
174 - 'contact' => $this->get_bool_option( $menu, 'contact', true ),
175 - 'support' => $this->get_bool_option( $menu, 'support', true ),
176 - 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ),
177 - 'account' => $this->get_bool_option( $menu, 'account', true ),
178 - 'pricing' => $this->get_bool_option( $menu, 'pricing', true ),
179 - 'addons' => $this->get_bool_option( $menu, 'addons', true ),
180 - );
181 -
182 - // @deprecated
183 - $this->_type = $this->get_option( $menu, 'type', 'page' );
184 - }
185 -
186 - $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' );
187 -
188 - if ( isset( $menu['parent'] ) ) {
189 - $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' );
190 - // @deprecated
191 - $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' );
192 -
193 - // If parent's slug is different, then it's NOT a top level menu item.
194 - $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug );
195 - } else {
196 - /**
197 - * If no parent then top level if:
198 - * - Has custom admin menu ('page')
199 - * - CPT menu type ('cpt')
200 - */
201 -// $this->_is_top_level = in_array( $this->_type, array(
202 -// 'cpt',
203 -// 'page'
204 -// ) );
205 - }
206 -
207 - $first_path = $this->get_option( $menu, 'first-path', false );
208 -
209 - if ( ! empty( $first_path ) && is_string( $first_path ) ) {
210 - $this->_first_time_path = $first_path;
211 - }
212 - }
213 - }
214 -
215 - /**
216 - * Check if top level menu.
217 - *
218 - * @author Vova Feldman (@svovaf)
219 - * @since 1.1.3
220 - *
221 - * @return bool False if submenu item.
222 - */
223 - function is_top_level() {
224 - return $this->_is_top_level;
225 - }
226 -
227 - /**
228 - * Check if the page should be override on exact URL match.
229 - *
230 - * @author Vova Feldman (@svovaf)
231 - * @since 1.1.3
232 - *
233 - * @return bool False if submenu item.
234 - */
235 - function is_override_exact() {
236 - return $this->_is_override_exact;
237 - }
238 -
239 -
240 - /**
241 - * Get the path of the page the user should be forwarded to after first activation.
242 - *
243 - * @author Vova Feldman (@svovaf)
244 - * @since 1.1.3
245 - *
246 - * @param bool $is_network Since 2.4.5
247 - *
248 - * @return string
249 - */
250 - function get_first_time_path( $is_network = false ) {
251 - if ( empty ( $this->_first_time_path ) ) {
252 - return $this->_first_time_path;
253 - }
254 -
255 - if ( $is_network ) {
256 - return network_admin_url( $this->_first_time_path );
257 - } else {
258 - return admin_url( $this->_first_time_path );
259 - }
260 - }
261 -
262 - /**
263 - * Check if plugin's menu item is part of a custom top level menu.
264 - *
265 - * @author Vova Feldman (@svovaf)
266 - * @since 1.1.3
267 - *
268 - * @return bool
269 - */
270 - function has_custom_parent() {
271 - return ! $this->_is_top_level && is_string( $this->_parent_slug );
272 - }
273 -
274 - /**
275 - * @author Leo Fajardo (@leorw)
276 - * @since 1.2.2
277 - *
278 - * @return bool
279 - */
280 - function has_menu() {
281 - return $this->_menu_exists;
282 - }
283 -
284 - /**
285 - * @author Vova Feldman (@svovaf)
286 - * @since 2.0.0
287 - *
288 - * @return bool
289 - */
290 - function has_network_menu() {
291 - return $this->_network_menu_exists;
292 - }
293 -
294 - /**
295 - * @author Leo Fajardo (@leorw)
296 - *
297 - * @param string $menu_slug
298 - *
299 - * @since 2.1.3
300 - */
301 - function set_slug_and_network_menu_exists_flag($menu_slug ) {
302 - $this->_menu_slug = $menu_slug;
303 - $this->_network_menu_exists = false;
304 - }
305 -
306 - /**
307 - * @author Vova Feldman (@svovaf)
308 - * @since 1.1.3
309 - *
310 - * @param string $id
311 - * @param bool $default
312 - * @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.
313 - *
314 - * @return bool
315 - */
316 - function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) {
317 - if ( ! $ignore_menu_existence && ! $this->has_menu() ) {
318 - return false;
319 - }
320 -
321 - return fs_apply_filter(
322 - $this->_module_unique_affix,
323 - 'is_submenu_visible',
324 - $this->get_bool_option( $this->_default_submenu_items, $id, $default ),
325 - $id
326 - );
327 - }
328 -
329 - /**
330 - * Calculates admin settings menu slug.
331 - * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug.
332 - *
333 - * @author Vova Feldman (@svovaf)
334 - * @since 1.1.3
335 - *
336 - * @param string $page
337 - *
338 - * @return string
339 - */
340 - function get_slug( $page = '' ) {
341 - return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ?
342 - $this->_menu_slug :
343 - $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) );
344 - }
345 -
346 - /**
347 - * @author Vova Feldman (@svovaf)
348 - * @since 1.1.3
349 - *
350 - * @return string
351 - */
352 - function get_parent_slug() {
353 - return $this->_parent_slug;
354 - }
355 -
356 - /**
357 - * @author Vova Feldman (@svovaf)
358 - * @since 1.1.3
359 - *
360 - * @return string
361 - */
362 - function get_type() {
363 - return $this->_type;
364 - }
365 -
366 - /**
367 - * @author Vova Feldman (@svovaf)
368 - * @since 1.1.3
369 - *
370 - * @return bool
371 - */
372 - function is_cpt() {
373 - return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) ||
374 - // Back compatibility.
375 - 'cpt' === $this->_type
376 - );
377 - }
378 -
379 - /**
380 - * @author Vova Feldman (@svovaf)
381 - * @since 1.1.3
382 - *
383 - * @return string
384 - */
385 - function get_parent_type() {
386 - return $this->_parent_type;
387 - }
388 -
389 - /**
390 - * @author Vova Feldman (@svovaf)
391 - * @since 1.1.3
392 - *
393 - * @return string
394 - */
395 - function get_raw_slug() {
396 - return $this->_menu_slug;
397 - }
398 -
399 - /**
400 - * Get plugin's original menu slug.
401 - *
402 - * @author Vova Feldman (@svovaf)
403 - * @since 1.1.3
404 - *
405 - * @return string
406 - */
407 - function get_original_menu_slug() {
408 - if ( 'cpt' === $this->_type ) {
409 - return add_query_arg( array(
410 - 'post_type' => $this->_menu_slug
411 - ), 'edit.php' );
412 - }
413 -
414 - if ( false === strpos( $this->_menu_slug, '.php?' ) ) {
415 - return $this->_menu_slug;
416 - } else {
417 - return $this->_module_unique_affix;
418 - }
419 - }
420 -
421 - /**
422 - * @author Vova Feldman (@svovaf)
423 - * @since 1.1.3
424 - *
425 - * @return string
426 - */
427 - function get_top_level_menu_slug() {
428 - return $this->has_custom_parent() ?
429 - $this->get_parent_slug() :
430 - $this->get_raw_slug();
431 - }
432 -
433 - /**
434 - * Is user on plugin's admin activation page.
435 - *
436 - * @author Vova Feldman (@svovaf)
437 - * @since 1.0.8
438 - *
439 - * @param bool $show_opt_in_on_themes_page Since 2.3.1
440 - *
441 - * @return bool
442 - *
443 - * @deprecated Please use is_activation_page() instead.
444 - */
445 - function is_main_settings_page( $show_opt_in_on_themes_page = false ) {
446 - return $this->is_activation_page( $show_opt_in_on_themes_page );
447 - }
448 -
449 - /**
450 - * Is user on product's admin activation page.
451 - *
452 - * @author Vova Feldman (@svovaf)
453 - * @since 2.3.1
454 - *
455 - * @param bool $show_opt_in_on_themes_page Since 2.3.1
456 - *
457 - * @return bool
458 - */
459 - function is_activation_page( $show_opt_in_on_themes_page = false ) {
460 - if ( $show_opt_in_on_themes_page ) {
461 - /**
462 - * In activation only when show_optin query string param is given.
463 - *
464 - * @since 1.2.2
465 - */
466 - return (
467 - ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) &&
468 - Freemius::is_themes_page() &&
469 - fs_request_get_bool( $this->_module_unique_affix . '_show_optin' )
470 - );
471 - }
472 -
473 - if ( $this->_menu_exists &&
474 - ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) )
475 - ) {
476 - /**
477 - * Module has a settings menu and the context page is the main settings page, so assume it's in
478 - * activation (doesn't really check if already opted-in/skipped or not).
479 - *
480 - * @since 1.2.2
481 - */
482 - return true;
483 - }
484 -
485 - return false;
486 - }
487 -
488 - #region Submenu Override
489 -
490 - /**
491 - * Override submenu's action.
492 - *
493 - * @author Vova Feldman (@svovaf)
494 - * @since 1.1.0
495 - *
496 - * @param string $parent_slug
497 - * @param string $menu_slug
498 - * @param callable $function
499 - *
500 - * @return false|string If submenu exist, will return the hook name.
501 - */
502 - function override_submenu_action( $parent_slug, $menu_slug, $function ) {
503 - global $submenu;
504 -
505 - $menu_slug = plugin_basename( $menu_slug );
506 - $parent_slug = plugin_basename( $parent_slug );
507 -
508 - if ( ! isset( $submenu[ $parent_slug ] ) ) {
509 - // Parent menu not exist.
510 - return false;
511 - }
512 -
513 - $found_submenu_item = false;
514 - foreach ( $submenu[ $parent_slug ] as $submenu_item ) {
515 - if ( $menu_slug === $submenu_item[2] ) {
516 - $found_submenu_item = $submenu_item;
517 - break;
518 - }
519 - }
520 -
521 - if ( false === $found_submenu_item ) {
522 - // Submenu item not found.
523 - return false;
524 - }
525 -
526 - // Remove current function.
527 - $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug );
528 - remove_all_actions( $hookname );
529 -
530 - // Attach new action.
531 - add_action( $hookname, $function );
532 -
533 - return $hookname;
534 - }
535 -
536 - #endregion Submenu Override
537 -
538 - #region Top level menu Override
539 -
540 - /**
541 - * Find plugin's admin dashboard main menu item.
542 - *
543 - * @author Vova Feldman (@svovaf)
544 - * @since 1.0.2
545 - *
546 - * @return string[]|false
547 - */
548 - private function find_top_level_menu() {
549 - global $menu;
550 -
551 - $position = - 1;
552 - $found_menu = false;
553 -
554 - $menu_slug = $this->get_raw_slug();
555 -
556 - $hook_name = get_plugin_page_hookname( $menu_slug, '' );
557 - foreach ( $menu as $pos => $m ) {
558 - if ( $menu_slug === $m[2] ) {
559 - $position = $pos;
560 - $found_menu = $m;
561 - break;
562 - }
563 - }
564 -
565 - if ( false === $found_menu ) {
566 - return false;
567 - }
568 -
569 - return array(
570 - 'menu' => $found_menu,
571 - 'position' => $position,
572 - 'hook_name' => $hook_name
573 - );
574 - }
575 -
576 - /**
577 - * Find plugin's admin dashboard main submenu item.
578 - *
579 - * @author Vova Feldman (@svovaf)
580 - * @since 1.2.1.6
581 - *
582 - * @return array|false
583 - */
584 - private function find_main_submenu() {
585 - global $submenu;
586 -
587 - $top_level_menu_slug = $this->get_top_level_menu_slug();
588 -
589 - if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) {
590 - return false;
591 - }
592 -
593 - $submenu_slug = $this->get_raw_slug();
594 -
595 - $position = - 1;
596 - $found_submenu = false;
597 -
598 - $hook_name = get_plugin_page_hookname( $submenu_slug, '' );
599 -
600 - foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) {
601 - if ( $submenu_slug === $sub[2] ) {
602 - $position = $pos;
603 - $found_submenu = $sub;
604 - }
605 - }
606 -
607 - if ( false === $found_submenu ) {
608 - return false;
609 - }
610 -
611 - return array(
612 - 'menu' => $found_submenu,
613 - 'parent_slug' => $top_level_menu_slug,
614 - 'position' => $position,
615 - 'hook_name' => $hook_name
616 - );
617 - }
618 -
619 - /**
620 - * Remove all sub-menu items.
621 - *
622 - * @author Vova Feldman (@svovaf)
623 - * @since 1.0.7
624 - *
625 - * @return bool If submenu with plugin's menu slug was found.
626 - */
627 - private function remove_all_submenu_items() {
628 - global $submenu;
629 -
630 - $menu_slug = $this->get_raw_slug();
631 -
632 - if ( ! isset( $submenu[ $menu_slug ] ) ) {
633 - return false;
634 - }
635 -
636 - /**
637 - * This method is NOT executed for WordPress.org themes.
638 - * Since we maintain only one version of the SDK we added this small
639 - * hack to avoid the error from Theme Check since it's a false-positive.
640 - *
641 - * @author Vova Feldman (@svovaf)
642 - * @since 1.2.2.7
643 - */
644 - $submenu_ref = &$submenu;
645 - $submenu_ref[ $menu_slug ] = array();
646 -
647 - return true;
648 - }
649 -
650 - /**
651 - *
652 - * @author Vova Feldman (@svovaf)
653 - * @since 1.0.9
654 - *
655 - * @param bool $remove_top_level_menu
656 - *
657 - * @return false|array[string]mixed
658 - */
659 - function remove_menu_item( $remove_top_level_menu = false ) {
660 - $this->_logger->entrance();
661 -
662 - // Find main menu item.
663 - $top_level_menu = $this->find_top_level_menu();
664 -
665 - if ( false === $top_level_menu ) {
666 - return false;
667 - }
668 -
669 - // Remove it with its actions.
670 - remove_all_actions( $top_level_menu['hook_name'] );
671 -
672 - // Remove all submenu items.
673 - $this->remove_all_submenu_items();
674 -
675 - if ( $remove_top_level_menu ) {
676 - global $menu;
677 - unset( $menu[ $top_level_menu['position'] ] );
678 - }
679 -
680 - return $top_level_menu;
681 - }
682 -
683 - /**
684 - * Get module's main admin setting page URL.
685 - *
686 - * @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.
687 - *
688 - * @author Vova Feldman (@svovaf)
689 - * @since 1.2.2.7
690 - *
691 - * @return string
692 - */
693 - function main_menu_url() {
694 - $this->_logger->entrance();
695 -
696 - if ( $this->_is_top_level ) {
697 - $menu = $this->find_top_level_menu();
698 - } else {
699 - $menu = $this->find_main_submenu();
700 - }
701 -
702 - $parent_slug = isset( $menu['parent_slug'] ) ?
703 - $menu['parent_slug'] :
704 - 'admin.php';
705 -
706 - return admin_url(
707 - $parent_slug .
708 - ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) .
709 - 'page=' .
710 - $menu['menu'][2]
711 - );
712 - }
713 -
714 - /**
715 - * @author Vova Feldman (@svovaf)
716 - * @since 1.1.4
717 - *
718 - * @param callable $function
719 - *
720 - * @return false|array[string]mixed
721 - */
722 - function override_menu_item( $function ) {
723 - $found_menu = $this->remove_menu_item();
724 -
725 - if ( false === $found_menu ) {
726 - return false;
727 - }
728 -
729 - if ( ! $this->is_top_level() || ! $this->is_cpt() ) {
730 - $menu_slug = plugin_basename( $this->get_slug() );
731 -
732 - $hookname = get_plugin_page_hookname( $menu_slug, '' );
733 -
734 - // Override menu action.
735 - add_action( $hookname, $function );
736 - } else {
737 - global $menu;
738 -
739 - // Remove original CPT menu.
740 - unset( $menu[ $found_menu['position'] ] );
741 -
742 - // Create new top-level menu action.
743 - $hookname = self::add_page(
744 - $found_menu['menu'][3],
745 - $found_menu['menu'][0],
746 - 'manage_options',
747 - $this->get_slug(),
748 - $function,
749 - $found_menu['menu'][6],
750 - $found_menu['position']
751 - );
752 - }
753 -
754 - return $hookname;
755 - }
756 -
757 - /**
758 - * Adds a counter to the module's top level menu item.
759 - *
760 - * @author Vova Feldman (@svovaf)
761 - * @since 1.2.1.5
762 - *
763 - * @param int $counter
764 - * @param string $class
765 - */
766 - function add_counter_to_menu_item( $counter = 1, $class = '' ) {
767 - global $menu, $submenu;
768 -
769 - $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>';
770 -
771 - /**
772 - * This method is NOT executed for WordPress.org themes.
773 - * Since we maintain only one version of the SDK we added this small
774 - * hack to avoid the error from Theme Check since it's a false-positive.
775 - *
776 - * @author Vova Feldman (@svovaf)
777 - * @since 1.2.2.7
778 - */
779 - $menu_ref = &$menu;
780 - $submenu_ref = &$submenu;
781 -
782 - if ( $this->_is_top_level ) {
783 - // Find main menu item.
784 - $found_menu = $this->find_top_level_menu();
785 -
786 - if ( false !== $found_menu ) {
787 - // Override menu label.
788 - $menu_ref[ $found_menu['position'] ][0] = sprintf(
789 - $mask,
790 - $found_menu['menu'][0],
791 - $class,
792 - $counter
793 - );
794 - }
795 - } else {
796 - $found_submenu = $this->find_main_submenu();
797 -
798 - if ( false !== $found_submenu ) {
799 - // Override menu label.
800 - $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf(
801 - $mask,
802 - $found_submenu['menu'][0],
803 - $class,
804 - $counter
805 - );
806 - }
807 - }
808 - }
809 -
810 - #endregion Top level menu Override
811 -
812 - /**
813 - * Add a top-level menu page.
814 - *
815 - * Note for WordPress.org Theme/Plugin reviewer:
816 - *
817 - * This is a replication of `add_menu_page()` to avoid Theme Check warning.
818 - *
819 - * Why?
820 - * ====
821 - * Freemius is an SDK for plugin and theme developers. Since the core
822 - * of the SDK is relevant both for plugins and themes, for obvious reasons,
823 - * we only develop and maintain one code base.
824 - *
825 - * This method will not run for wp.org themes (only plugins) since theme
826 - * admin settings/options are now only allowed in the customizer.
827 - *
828 - * If you have any questions or need clarifications, please don't hesitate
829 - * pinging me on slack, my username is @svovaf.
830 - *
831 - * @author Vova Feldman (@svovaf)
832 - * @since 1.2.2
833 - *
834 - * @param string $page_title The text to be displayed in the title tags of the page when the menu is
835 - * selected.
836 - * @param string $menu_title The text to be used for the menu.
837 - * @param string $capability The capability required for this menu to be displayed to the user.
838 - * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
839 - * @param callable|string $function The function to be called to output the content for this page.
840 - * @param string $icon_url The URL to the icon to be used for this menu.
841 - * * Pass a base64-encoded SVG using a data URI, which will be colored to
842 - * match the color scheme. This should begin with
843 - * 'data:image/svg+xml;base64,'.
844 - * * Pass the name of a Dashicons helper class to use a font icon,
845 - * e.g. 'dashicons-chart-pie'.
846 - * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added
847 - * via CSS.
848 - * @param int $position The position in the menu order this one should appear.
849 - *
850 - * @return string The resulting page's hook_suffix.
851 - */
852 - static function add_page(
853 - $page_title,
854 - $menu_title,
855 - $capability,
856 - $menu_slug,
857 - $function = '',
858 - $icon_url = '',
859 - $position = null
860 - ) {
861 - $fn = 'add_menu' . '_page';
862 -
863 - return $fn(
864 - $page_title,
865 - $menu_title,
866 - $capability,
867 - $menu_slug,
868 - $function,
869 - $icon_url,
870 - $position
871 - );
872 - }
873 -
874 - /**
875 - * Add page and update menu instance settings.
876 - *
877 - * @author Vova Feldman (@svovaf)
878 - * @since 2.0.0
879 - *
880 - * @param string $page_title
881 - * @param string $menu_title
882 - * @param string $capability
883 - * @param string $menu_slug
884 - * @param callable|string $function
885 - * @param string $icon_url
886 - * @param int|null $position
887 - *
888 - * @return string
889 - */
890 - function add_page_and_update(
891 - $page_title,
892 - $menu_title,
893 - $capability,
894 - $menu_slug,
895 - $function = '',
896 - $icon_url = '',
897 - $position = null
898 - ) {
899 - $this->_menu_slug = $menu_slug;
900 - $this->_is_top_level = true;
901 - $this->_menu_exists = true;
902 - $this->_network_menu_exists = true;
903 -
904 - return self::add_page(
905 - $page_title,
906 - $menu_title,
907 - $capability,
908 - $menu_slug,
909 - $function,
910 - $icon_url,
911 - $position
912 - );
913 - }
914 -
915 - /**
916 - * Add a submenu page.
917 - *
918 - * Note for WordPress.org Theme/Plugin reviewer:
919 - *
920 - * This is a replication of `add_submenu_page()` to avoid Theme Check warning.
921 - *
922 - * Why?
923 - * ====
924 - * Freemius is an SDK for plugin and theme developers. Since the core
925 - * of the SDK is relevant both for plugins and themes, for obvious reasons,
926 - * we only develop and maintain one code base.
927 - *
928 - * This method will not run for wp.org themes (only plugins) since theme
929 - * admin settings/options are now only allowed in the customizer.
930 - *
931 - * If you have any questions or need clarifications, please don't hesitate
932 - * pinging me on slack, my username is @svovaf.
933 - *
934 - * @author Vova Feldman (@svovaf)
935 - * @since 1.2.2
936 - *
937 - * @param string $parent_slug The slug name for the parent menu (or the file name of a standard
938 - * WordPress admin page).
939 - * @param string $page_title The text to be displayed in the title tags of the page when the menu is
940 - * selected.
941 - * @param string $menu_title The text to be used for the menu.
942 - * @param string $capability The capability required for this menu to be displayed to the user.
943 - * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
944 - * @param callable|string $function The function to be called to output the content for this page.
945 - *
946 - * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability
947 - * required.
948 - */
949 - static function add_subpage(
950 - $parent_slug,
951 - $page_title,
952 - $menu_title,
953 - $capability,
954 - $menu_slug,
955 - $function = ''
956 - ) {
957 - $fn = 'add_submenu' . '_page';
958 -
959 - return $fn( $parent_slug,
960 - $page_title,
961 - $menu_title,
962 - $capability,
963 - $menu_slug,
964 - $function
965 - );
966 - }
967 -
968 - /**
969 - * Add sub page and update menu instance settings.
970 - *
971 - * @author Vova Feldman (@svovaf)
972 - * @since 2.0.0
973 - *
974 - * @param string $parent_slug
975 - * @param string $page_title
976 - * @param string $menu_title
977 - * @param string $capability
978 - * @param string $menu_slug
979 - * @param callable|string $function
980 - *
981 - * @return string
982 - */
983 - function add_subpage_and_update(
984 - $parent_slug,
985 - $page_title,
986 - $menu_title,
987 - $capability,
988 - $menu_slug,
989 - $function = ''
990 - ) {
991 - $this->_menu_slug = $menu_slug;
992 - $this->_parent_slug = $parent_slug;
993 - $this->_is_top_level = false;
994 - $this->_menu_exists = true;
995 - $this->_network_menu_exists = true;
996 -
997 - return self::add_subpage(
998 - $parent_slug,
999 - $page_title,
1000 - $menu_title,
1001 - $capability,
1002 - $menu_slug,
1003 - $function
1004 - );
1005 - }
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 + * @since 2.0.0
94 + *
95 + * @var bool
96 + */
97 + private $_network_menu_exists;
98 +
99 + #endregion Properties
100 +
101 + /**
102 + * @var FS_Logger
103 + */
104 + protected $_logger;
105 +
106 + #region Singleton
107 +
108 + /**
109 + * @var FS_Admin_Menu_Manager[]
110 + */
111 + private static $_instances = array();
112 +
113 + /**
114 + * @param number $module_id
115 + * @param string $module_type
116 + * @param string $module_unique_affix
117 + *
118 + * @return FS_Admin_Menu_Manager
119 + */
120 + static function instance( $module_id, $module_type, $module_unique_affix ) {
121 + $key = 'm_' . $module_id;
122 +
123 + if ( ! isset( self::$_instances[ $key ] ) ) {
124 + self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix );
125 + }
126 +
127 + return self::$_instances[ $key ];
128 + }
129 +
130 + protected function __construct( $module_id, $module_type, $module_unique_affix ) {
131 + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
132 +
133 + $this->_module_id = $module_id;
134 + $this->_module_type = $module_type;
135 + $this->_module_unique_affix = $module_unique_affix;
136 + }
137 +
138 + #endregion Singleton
139 +
140 + #region Helpers
141 +
142 + private function get_option( &$options, $key, $default = false ) {
143 + return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
144 + }
145 +
146 + private function get_bool_option( &$options, $key, $default = false ) {
147 + return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
148 + }
149 +
150 + #endregion Helpers
151 +
152 + /**
153 + * @param array $menu
154 + * @param bool $is_addon
155 + */
156 + function init( $menu, $is_addon = false ) {
157 + $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) );
158 + $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] );
159 +
160 + $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix );
161 +
162 + $this->_default_submenu_items = array();
163 + // @deprecated
164 + $this->_type = 'page';
165 + $this->_is_top_level = true;
166 + $this->_is_override_exact = false;
167 + $this->_parent_slug = false;
168 + // @deprecated
169 + $this->_parent_type = 'page';
170 +
171 + if ( isset( $menu ) ) {
172 + if ( ! $is_addon ) {
173 + $this->_default_submenu_items = array(
174 + 'contact' => $this->get_bool_option( $menu, 'contact', true ),
175 + 'support' => $this->get_bool_option( $menu, 'support', true ),
176 + 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ),
177 + 'account' => $this->get_bool_option( $menu, 'account', true ),
178 + 'pricing' => $this->get_bool_option( $menu, 'pricing', true ),
179 + 'addons' => $this->get_bool_option( $menu, 'addons', true ),
180 + );
181 +
182 + // @deprecated
183 + $this->_type = $this->get_option( $menu, 'type', 'page' );
184 + }
185 +
186 + $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' );
187 +
188 + if ( isset( $menu['parent'] ) ) {
189 + $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' );
190 + // @deprecated
191 + $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' );
192 +
193 + // If parent's slug is different, then it's NOT a top level menu item.
194 + $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug );
195 + } else {
196 + /**
197 + * If no parent then top level if:
198 + * - Has custom admin menu ('page')
199 + * - CPT menu type ('cpt')
200 + */
201 +// $this->_is_top_level = in_array( $this->_type, array(
202 +// 'cpt',
203 +// 'page'
204 +// ) );
205 + }
206 +
207 + $first_path = $this->get_option( $menu, 'first-path', false );
208 +
209 + if ( ! empty( $first_path ) && is_string( $first_path ) ) {
210 + $this->_first_time_path = $first_path;
211 + }
212 + }
213 + }
214 +
215 + /**
216 + * Check if top level menu.
217 + *
218 + * @author Vova Feldman (@svovaf)
219 + * @since 1.1.3
220 + *
221 + * @return bool False if submenu item.
222 + */
223 + function is_top_level() {
224 + return $this->_is_top_level;
225 + }
226 +
227 + /**
228 + * Check if the page should be override on exact URL match.
229 + *
230 + * @author Vova Feldman (@svovaf)
231 + * @since 1.1.3
232 + *
233 + * @return bool False if submenu item.
234 + */
235 + function is_override_exact() {
236 + return $this->_is_override_exact;
237 + }
238 +
239 +
240 + /**
241 + * Get the path of the page the user should be forwarded to after first activation.
242 + *
243 + * @author Vova Feldman (@svovaf)
244 + * @since 1.1.3
245 + *
246 + * @param bool $is_network Since 2.4.5
247 + *
248 + * @return string
249 + */
250 + function get_first_time_path( $is_network = false ) {
251 + if ( empty ( $this->_first_time_path ) ) {
252 + return $this->_first_time_path;
253 + }
254 +
255 + if ( $is_network ) {
256 + return network_admin_url( $this->_first_time_path );
257 + } else {
258 + return admin_url( $this->_first_time_path );
259 + }
260 + }
261 +
262 + /**
263 + * Check if plugin's menu item is part of a custom top level menu.
264 + *
265 + * @author Vova Feldman (@svovaf)
266 + * @since 1.1.3
267 + *
268 + * @return bool
269 + */
270 + function has_custom_parent() {
271 + return ! $this->_is_top_level && is_string( $this->_parent_slug );
272 + }
273 +
274 + /**
275 + * @author Leo Fajardo (@leorw)
276 + * @since 1.2.2
277 + *
278 + * @return bool
279 + */
280 + function has_menu() {
281 + return $this->_menu_exists;
282 + }
283 +
284 + /**
285 + * @author Vova Feldman (@svovaf)
286 + * @since 2.0.0
287 + *
288 + * @return bool
289 + */
290 + function has_network_menu() {
291 + return $this->_network_menu_exists;
292 + }
293 +
294 + /**
295 + * @author Leo Fajardo (@leorw)
296 + *
297 + * @param string $menu_slug
298 + *
299 + * @since 2.1.3
300 + */
301 + function set_slug_and_network_menu_exists_flag($menu_slug ) {
302 + $this->_menu_slug = $menu_slug;
303 + $this->_network_menu_exists = false;
304 + }
305 +
306 + /**
307 + * @author Vova Feldman (@svovaf)
308 + * @since 1.1.3
309 + *
310 + * @param string $id
311 + * @param bool $default
312 + * @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.
313 + *
314 + * @return bool
315 + */
316 + function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) {
317 + if ( ! $ignore_menu_existence && ! $this->has_menu() ) {
318 + return false;
319 + }
320 +
321 + return fs_apply_filter(
322 + $this->_module_unique_affix,
323 + 'is_submenu_visible',
324 + $this->get_bool_option( $this->_default_submenu_items, $id, $default ),
325 + $id
326 + );
327 + }
328 +
329 + /**
330 + * Calculates admin settings menu slug.
331 + * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug.
332 + *
333 + * @author Vova Feldman (@svovaf)
334 + * @since 1.1.3
335 + *
336 + * @param string $page
337 + *
338 + * @return string
339 + */
340 + function get_slug( $page = '' ) {
341 + return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ?
342 + $this->_menu_slug :
343 + $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) );
344 + }
345 +
346 + /**
347 + * @author Vova Feldman (@svovaf)
348 + * @since 1.1.3
349 + *
350 + * @return string
351 + */
352 + function get_parent_slug() {
353 + return $this->_parent_slug;
354 + }
355 +
356 + /**
357 + * @author Vova Feldman (@svovaf)
358 + * @since 1.1.3
359 + *
360 + * @return string
361 + */
362 + function get_type() {
363 + return $this->_type;
364 + }
365 +
366 + /**
367 + * @author Vova Feldman (@svovaf)
368 + * @since 1.1.3
369 + *
370 + * @return bool
371 + */
372 + function is_cpt() {
373 + return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) ||
374 + // Back compatibility.
375 + 'cpt' === $this->_type
376 + );
377 + }
378 +
379 + /**
380 + * @author Vova Feldman (@svovaf)
381 + * @since 1.1.3
382 + *
383 + * @return string
384 + */
385 + function get_parent_type() {
386 + return $this->_parent_type;
387 + }
388 +
389 + /**
390 + * @author Vova Feldman (@svovaf)
391 + * @since 1.1.3
392 + *
393 + * @return string
394 + */
395 + function get_raw_slug() {
396 + return $this->_menu_slug;
397 + }
398 +
399 + /**
400 + * Get plugin's original menu slug.
401 + *
402 + * @author Vova Feldman (@svovaf)
403 + * @since 1.1.3
404 + *
405 + * @return string
406 + */
407 + function get_original_menu_slug() {
408 + if ( 'cpt' === $this->_type ) {
409 + return add_query_arg( array(
410 + 'post_type' => $this->_menu_slug
411 + ), 'edit.php' );
412 + }
413 +
414 + if ( false === strpos( $this->_menu_slug, '.php?' ) ) {
415 + return $this->_menu_slug;
416 + } else {
417 + return $this->_module_unique_affix;
418 + }
419 + }
420 +
421 + /**
422 + * @author Vova Feldman (@svovaf)
423 + * @since 1.1.3
424 + *
425 + * @return string
426 + */
427 + function get_top_level_menu_slug() {
428 + return $this->has_custom_parent() ?
429 + $this->get_parent_slug() :
430 + $this->get_raw_slug();
431 + }
432 +
433 + /**
434 + * Is user on plugin's admin activation page.
435 + *
436 + * @author Vova Feldman (@svovaf)
437 + * @since 1.0.8
438 + *
439 + * @param bool $show_opt_in_on_themes_page Since 2.3.1
440 + *
441 + * @return bool
442 + *
443 + * @deprecated Please use is_activation_page() instead.
444 + */
445 + function is_main_settings_page( $show_opt_in_on_themes_page = false ) {
446 + return $this->is_activation_page( $show_opt_in_on_themes_page );
447 + }
448 +
449 + /**
450 + * Is user on product's admin activation page.
451 + *
452 + * @author Vova Feldman (@svovaf)
453 + * @since 2.3.1
454 + *
455 + * @param bool $show_opt_in_on_themes_page Since 2.3.1
456 + *
457 + * @return bool
458 + */
459 + function is_activation_page( $show_opt_in_on_themes_page = false ) {
460 + if ( $show_opt_in_on_themes_page ) {
461 + /**
462 + * In activation only when show_optin query string param is given.
463 + *
464 + * @since 1.2.2
465 + */
466 + return (
467 + ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) &&
468 + Freemius::is_themes_page() &&
469 + fs_request_get_bool( $this->_module_unique_affix . '_show_optin' )
470 + );
471 + }
472 +
473 + if ( $this->_menu_exists &&
474 + ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) )
475 + ) {
476 + /**
477 + * Module has a settings menu and the context page is the main settings page, so assume it's in
478 + * activation (doesn't really check if already opted-in/skipped or not).
479 + *
480 + * @since 1.2.2
481 + */
482 + return true;
483 + }
484 +
485 + return false;
486 + }
487 +
488 + #region Submenu Override
489 +
490 + /**
491 + * Override submenu's action.
492 + *
493 + * @author Vova Feldman (@svovaf)
494 + * @since 1.1.0
495 + *
496 + * @param string $parent_slug
497 + * @param string $menu_slug
498 + * @param callable $function
499 + *
500 + * @return false|string If submenu exist, will return the hook name.
501 + */
502 + function override_submenu_action( $parent_slug, $menu_slug, $function ) {
503 + global $submenu;
504 +
505 + $menu_slug = plugin_basename( $menu_slug );
506 + $parent_slug = plugin_basename( $parent_slug );
507 +
508 + if ( ! isset( $submenu[ $parent_slug ] ) ) {
509 + // Parent menu not exist.
510 + return false;
511 + }
512 +
513 + $found_submenu_item = false;
514 + foreach ( $submenu[ $parent_slug ] as $submenu_item ) {
515 + if ( $menu_slug === $submenu_item[2] ) {
516 + $found_submenu_item = $submenu_item;
517 + break;
518 + }
519 + }
520 +
521 + if ( false === $found_submenu_item ) {
522 + // Submenu item not found.
523 + return false;
524 + }
525 +
526 + // Remove current function.
527 + $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug );
528 + remove_all_actions( $hookname );
529 +
530 + // Attach new action.
531 + add_action( $hookname, $function );
532 +
533 + return $hookname;
534 + }
535 +
536 + #endregion Submenu Override
537 +
538 + #region Top level menu Override
539 +
540 + /**
541 + * Find plugin's admin dashboard main menu item.
542 + *
543 + * @author Vova Feldman (@svovaf)
544 + * @since 1.0.2
545 + *
546 + * @return string[]|false
547 + */
548 + private function find_top_level_menu() {
549 + global $menu;
550 +
551 + $position = - 1;
552 + $found_menu = false;
553 +
554 + $menu_slug = $this->get_raw_slug();
555 +
556 + $hook_name = get_plugin_page_hookname( $menu_slug, '' );
557 + foreach ( $menu as $pos => $m ) {
558 + if ( $menu_slug === $m[2] ) {
559 + $position = $pos;
560 + $found_menu = $m;
561 + break;
562 + }
563 + }
564 +
565 + if ( false === $found_menu ) {
566 + return false;
567 + }
568 +
569 + return array(
570 + 'menu' => $found_menu,
571 + 'position' => $position,
572 + 'hook_name' => $hook_name
573 + );
574 + }
575 +
576 + /**
577 + * Find plugin's admin dashboard main submenu item.
578 + *
579 + * @author Vova Feldman (@svovaf)
580 + * @since 1.2.1.6
581 + *
582 + * @return array|false
583 + */
584 + private function find_main_submenu() {
585 + global $submenu;
586 +
587 + $top_level_menu_slug = $this->get_top_level_menu_slug();
588 +
589 + if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) {
590 + return false;
591 + }
592 +
593 + $submenu_slug = $this->get_raw_slug();
594 +
595 + $position = - 1;
596 + $found_submenu = false;
597 +
598 + $hook_name = get_plugin_page_hookname( $submenu_slug, '' );
599 +
600 + foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) {
601 + if ( $submenu_slug === $sub[2] ) {
602 + $position = $pos;
603 + $found_submenu = $sub;
604 + }
605 + }
606 +
607 + if ( false === $found_submenu ) {
608 + return false;
609 + }
610 +
611 + return array(
612 + 'menu' => $found_submenu,
613 + 'parent_slug' => $top_level_menu_slug,
614 + 'position' => $position,
615 + 'hook_name' => $hook_name
616 + );
617 + }
618 +
619 + /**
620 + * Remove all sub-menu items.
621 + *
622 + * @author Vova Feldman (@svovaf)
623 + * @since 1.0.7
624 + *
625 + * @return bool If submenu with plugin's menu slug was found.
626 + */
627 + private function remove_all_submenu_items() {
628 + global $submenu;
629 +
630 + $menu_slug = $this->get_raw_slug();
631 +
632 + if ( ! isset( $submenu[ $menu_slug ] ) ) {
633 + return false;
634 + }
635 +
636 + /**
637 + * This method is NOT executed for WordPress.org themes.
638 + * Since we maintain only one version of the SDK we added this small
639 + * hack to avoid the error from Theme Check since it's a false-positive.
640 + *
641 + * @author Vova Feldman (@svovaf)
642 + * @since 1.2.2.7
643 + */
644 + $submenu_ref = &$submenu;
645 + $submenu_ref[ $menu_slug ] = array();
646 +
647 + return true;
648 + }
649 +
650 + /**
651 + *
652 + * @author Vova Feldman (@svovaf)
653 + * @since 1.0.9
654 + *
655 + * @param bool $remove_top_level_menu
656 + *
657 + * @return false|array[string]mixed
658 + */
659 + function remove_menu_item( $remove_top_level_menu = false ) {
660 + $this->_logger->entrance();
661 +
662 + // Find main menu item.
663 + $top_level_menu = $this->find_top_level_menu();
664 +
665 + if ( false === $top_level_menu ) {
666 + return false;
667 + }
668 +
669 + // Remove it with its actions.
670 + remove_all_actions( $top_level_menu['hook_name'] );
671 +
672 + // Remove all submenu items.
673 + $this->remove_all_submenu_items();
674 +
675 + if ( $remove_top_level_menu ) {
676 + global $menu;
677 + unset( $menu[ $top_level_menu['position'] ] );
678 + }
679 +
680 + return $top_level_menu;
681 + }
682 +
683 + /**
684 + * Get module's main admin setting page URL.
685 + *
686 + * @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.
687 + *
688 + * @author Vova Feldman (@svovaf)
689 + * @since 1.2.2.7
690 + *
691 + * @return string
692 + */
693 + function main_menu_url() {
694 + $this->_logger->entrance();
695 +
696 + if ( $this->_is_top_level ) {
697 + $menu = $this->find_top_level_menu();
698 + } else {
699 + $menu = $this->find_main_submenu();
700 + }
701 +
702 + $menu_slug = $menu['menu'][2];
703 + $parent_slug = isset( $menu['parent_slug'] ) ?
704 + $menu['parent_slug'] :
705 + 'admin.php';
706 +
707 + if ( fs_apply_filter( $this->_module_unique_affix, 'enable_cpt_advanced_menu_logic', false ) ) {
708 + $parent_slug = 'admin.php';
709 +
710 + /**
711 + * This line and the `if` block below it are based on the `menu_page_url()` function of WordPress.
712 + *
713 + * @author Leo Fajardo (@leorw)
714 + * @since 2.10.2
715 + */
716 + global $_parent_pages;
717 +
718 + if ( ! empty( $_parent_pages[ $menu_slug ] ) ) {
719 + $_parent_slug = $_parent_pages[ $menu_slug ];
720 + $parent_slug = isset( $_parent_pages[ $_parent_slug ] ) ?
721 + $parent_slug :
722 + $menu['parent_slug'];
723 + }
724 + }
725 +
726 + return admin_url(
727 + $parent_slug .
728 + ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) .
729 + 'page=' .
730 + $menu_slug
731 + );
732 + }
733 +
734 + /**
735 + * @author Vova Feldman (@svovaf)
736 + * @since 1.1.4
737 + *
738 + * @param callable $function
739 + *
740 + * @return false|array[string]mixed
741 + */
742 + function override_menu_item( $function ) {
743 + $found_menu = $this->remove_menu_item();
744 +
745 + if ( false === $found_menu ) {
746 + return false;
747 + }
748 +
749 + if ( ! $this->is_top_level() || ! $this->is_cpt() ) {
750 + $menu_slug = plugin_basename( $this->get_slug() );
751 +
752 + $hookname = get_plugin_page_hookname( $menu_slug, '' );
753 +
754 + // Override menu action.
755 + add_action( $hookname, $function );
756 + } else {
757 + global $menu;
758 +
759 + // Remove original CPT menu.
760 + unset( $menu[ $found_menu['position'] ] );
761 +
762 + // Create new top-level menu action.
763 + $hookname = self::add_page(
764 + $found_menu['menu'][3],
765 + $found_menu['menu'][0],
766 + 'manage_options',
767 + $this->get_slug(),
768 + $function,
769 + $found_menu['menu'][6],
770 + $found_menu['position']
771 + );
772 + }
773 +
774 + return $hookname;
775 + }
776 +
777 + /**
778 + * Adds a counter to the module's top level menu item.
779 + *
780 + * @author Vova Feldman (@svovaf)
781 + * @since 1.2.1.5
782 + *
783 + * @param int $counter
784 + * @param string $class
785 + */
786 + function add_counter_to_menu_item( $counter = 1, $class = '' ) {
787 + global $menu, $submenu;
788 +
789 + $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>';
790 +
791 + /**
792 + * This method is NOT executed for WordPress.org themes.
793 + * Since we maintain only one version of the SDK we added this small
794 + * hack to avoid the error from Theme Check since it's a false-positive.
795 + *
796 + * @author Vova Feldman (@svovaf)
797 + * @since 1.2.2.7
798 + */
799 + $menu_ref = &$menu;
800 + $submenu_ref = &$submenu;
801 +
802 + if ( $this->_is_top_level ) {
803 + // Find main menu item.
804 + $found_menu = $this->find_top_level_menu();
805 +
806 + if ( false !== $found_menu ) {
807 + // Override menu label.
808 + $menu_ref[ $found_menu['position'] ][0] = sprintf(
809 + $mask,
810 + $found_menu['menu'][0],
811 + $class,
812 + $counter
813 + );
814 + }
815 + } else {
816 + $found_submenu = $this->find_main_submenu();
817 +
818 + if ( false !== $found_submenu ) {
819 + // Override menu label.
820 + $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf(
821 + $mask,
822 + $found_submenu['menu'][0],
823 + $class,
824 + $counter
825 + );
826 + }
827 + }
828 + }
829 +
830 + #endregion Top level menu Override
831 +
832 + /**
833 + * Add a top-level menu page.
834 + *
835 + * Note for WordPress.org Theme/Plugin reviewer:
836 + *
837 + * This is a replication of `add_menu_page()` to avoid Theme Check warning.
838 + *
839 + * Why?
840 + * ====
841 + * Freemius is an SDK for plugin and theme developers. Since the core
842 + * of the SDK is relevant both for plugins and themes, for obvious reasons,
843 + * we only develop and maintain one code base.
844 + *
845 + * This method will not run for wp.org themes (only plugins) since theme
846 + * admin settings/options are now only allowed in the customizer.
847 + *
848 + * If you have any questions or need clarifications, please don't hesitate
849 + * pinging me on slack, my username is @svovaf.
850 + *
851 + * @author Vova Feldman (@svovaf)
852 + * @since 1.2.2
853 + *
854 + * @param string $page_title The text to be displayed in the title tags of the page when the menu is
855 + * selected.
856 + * @param string $menu_title The text to be used for the menu.
857 + * @param string $capability The capability required for this menu to be displayed to the user.
858 + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
859 + * @param callable|string $function The function to be called to output the content for this page.
860 + * @param string $icon_url The URL to the icon to be used for this menu.
861 + * * Pass a base64-encoded SVG using a data URI, which will be colored to
862 + * match the color scheme. This should begin with
863 + * 'data:image/svg+xml;base64,'.
864 + * * Pass the name of a Dashicons helper class to use a font icon,
865 + * e.g. 'dashicons-chart-pie'.
866 + * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added
867 + * via CSS.
868 + * @param int $position The position in the menu order this one should appear.
869 + *
870 + * @return string The resulting page's hook_suffix.
871 + */
872 + static function add_page(
873 + $page_title,
874 + $menu_title,
875 + $capability,
876 + $menu_slug,
877 + $function = '',
878 + $icon_url = '',
879 + $position = null
880 + ) {
881 + $fn = 'add_menu' . '_page';
882 +
883 + return $fn(
884 + $page_title,
885 + $menu_title,
886 + $capability,
887 + $menu_slug,
888 + $function,
889 + $icon_url,
890 + $position
891 + );
892 + }
893 +
894 + /**
895 + * Add page and update menu instance settings.
896 + *
897 + * @author Vova Feldman (@svovaf)
898 + * @since 2.0.0
899 + *
900 + * @param string $page_title
901 + * @param string $menu_title
902 + * @param string $capability
903 + * @param string $menu_slug
904 + * @param callable|string $function
905 + * @param string $icon_url
906 + * @param int|null $position
907 + *
908 + * @return string
909 + */
910 + function add_page_and_update(
911 + $page_title,
912 + $menu_title,
913 + $capability,
914 + $menu_slug,
915 + $function = '',
916 + $icon_url = '',
917 + $position = null
918 + ) {
919 + $this->_menu_slug = $menu_slug;
920 + $this->_is_top_level = true;
921 + $this->_menu_exists = true;
922 + $this->_network_menu_exists = true;
923 +
924 + return self::add_page(
925 + $page_title,
926 + $menu_title,
927 + $capability,
928 + $menu_slug,
929 + $function,
930 + $icon_url,
931 + $position
932 + );
933 + }
934 +
935 + /**
936 + * Add a submenu page.
937 + *
938 + * Note for WordPress.org Theme/Plugin reviewer:
939 + *
940 + * This is a replication of `add_submenu_page()` to avoid Theme Check warning.
941 + *
942 + * Why?
943 + * ====
944 + * Freemius is an SDK for plugin and theme developers. Since the core
945 + * of the SDK is relevant both for plugins and themes, for obvious reasons,
946 + * we only develop and maintain one code base.
947 + *
948 + * This method will not run for wp.org themes (only plugins) since theme
949 + * admin settings/options are now only allowed in the customizer.
950 + *
951 + * If you have any questions or need clarifications, please don't hesitate
952 + * pinging me on slack, my username is @svovaf.
953 + *
954 + * @author Vova Feldman (@svovaf)
955 + * @since 1.2.2
956 + *
957 + * @param string $parent_slug The slug name for the parent menu (or the file name of a standard
958 + * WordPress admin page).
959 + * @param string $page_title The text to be displayed in the title tags of the page when the menu is
960 + * selected.
961 + * @param string $menu_title The text to be used for the menu.
962 + * @param string $capability The capability required for this menu to be displayed to the user.
963 + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
964 + * @param callable|string $function The function to be called to output the content for this page.
965 + *
966 + * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability
967 + * required.
968 + */
969 + static function add_subpage(
970 + $parent_slug,
971 + $page_title,
972 + $menu_title,
973 + $capability,
974 + $menu_slug,
975 + $function = ''
976 + ) {
977 + $fn = 'add_submenu' . '_page';
978 +
979 + return $fn( $parent_slug,
980 + $page_title,
981 + $menu_title,
982 + $capability,
983 + $menu_slug,
984 + $function
985 + );
986 + }
987 +
988 + /**
989 + * Add sub page and update menu instance settings.
990 + *
991 + * @author Vova Feldman (@svovaf)
992 + * @since 2.0.0
993 + *
994 + * @param string $parent_slug
995 + * @param string $page_title
996 + * @param string $menu_title
997 + * @param string $capability
998 + * @param string $menu_slug
999 + * @param callable|string $function
1000 + *
1001 + * @return string
1002 + */
1003 + function add_subpage_and_update(
1004 + $parent_slug,
1005 + $page_title,
1006 + $menu_title,
1007 + $capability,
1008 + $menu_slug,
1009 + $function = ''
1010 + ) {
1011 + $this->_menu_slug = $menu_slug;
1012 + $this->_parent_slug = $parent_slug;
1013 + $this->_is_top_level = false;
1014 + $this->_menu_exists = true;
1015 + $this->_network_menu_exists = true;
1016 +
1017 + return self::add_subpage(
1018 + $parent_slug,
1019 + $page_title,
1020 + $menu_title,
1021 + $capability,
1022 + $menu_slug,
1023 + $function
1024 + );
1025 + }
1006 1026 }