GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

FS_Admin_Menu_Manager::remove_all_submenu_items()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 13 and the first side effect is on line 10.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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 {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
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 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
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 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
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;
0 ignored issues
show
Documentation Bug introduced by
The property $_parent_slug was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
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' );
0 ignored issues
show
Documentation introduced by
'page' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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' );
0 ignored issues
show
Documentation introduced by
'page' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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 {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
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(
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
202
//						'cpt',
203
//						'page'
204
//					) );
205
				}
206
207
                $this->_first_time_path = $this->get_option( $menu, 'first-path', false );
208
                if ( ! empty( $this->_first_time_path ) && is_string( $this->_first_time_path ) ) {
209
                    $this->_first_time_path = admin_url( $this->_first_time_path, 'admin' );
210
                }
211
			}
212
		}
213
214
		/**
215
		 * Check if top level menu.
216
		 *
217
		 * @author Vova Feldman (@svovaf)
218
		 * @since  1.1.3
219
		 *
220
		 * @return bool False if submenu item.
221
		 */
222
		function is_top_level() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
223
			return $this->_is_top_level;
224
		}
225
226
		/**
227
		 * Check if the page should be override on exact URL match.
228
		 *
229
		 * @author Vova Feldman (@svovaf)
230
		 * @since  1.1.3
231
		 *
232
		 * @return bool False if submenu item.
233
		 */
234
		function is_override_exact() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
235
			return $this->_is_override_exact;
236
		}
237
238
239
		/**
240
		 * Get the path of the page the user should be forwarded to after first activation.
241
		 *
242
		 * @author Vova Feldman (@svovaf)
243
		 * @since  1.1.3
244
		 *
245
		 * @return string
246
		 */
247
		function get_first_time_path() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
248
			return $this->_first_time_path;
249
		}
250
251
		/**
252
		 * Check if plugin's menu item is part of a custom top level menu.
253
		 *
254
		 * @author Vova Feldman (@svovaf)
255
		 * @since  1.1.3
256
		 *
257
		 * @return bool
258
		 */
259
		function has_custom_parent() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
260
			return ! $this->_is_top_level && is_string( $this->_parent_slug );
261
		}
262
263
		/**
264
		 * @author Leo Fajardo (@leorw)
265
		 * @since  1.2.2
266
		 *
267
		 * @return bool
268
		 */
269
		function has_menu() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
270
			return $this->_menu_exists;
271
		}
272
273
		/**
274
         * @author Vova Feldman (@svovaf)
275
		 * @since  2.0.0
276
		 *
277
		 * @return bool
278
		 */
279
		function has_network_menu() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
280
			return $this->_network_menu_exists;
281
		}
282
283
        /**
284
         * @author Leo Fajardo (@leorw)
285
         *
286
         * @param string $menu_slug
287
         *
288
         * @since 2.1.3
289
         */
290
		function set_slug_and_network_menu_exists_flag($menu_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
291
		    $this->_menu_slug           = $menu_slug;
292
		    $this->_network_menu_exists = false;
293
        }
294
295
		/**
296
		 * @author Vova Feldman (@svovaf)
297
		 * @since  1.1.3
298
		 *
299
		 * @param string $id
300
		 * @param bool   $default
301
		 * @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.
302
		 *
303
		 * @return bool
304
		 */
305
		function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
306
			if ( ! $ignore_menu_existence && ! $this->has_menu() ) {
307
				return false;
308
			}
309
310
			return fs_apply_filter(
311
				$this->_module_unique_affix,
312
				'is_submenu_visible',
313
				$this->get_bool_option( $this->_default_submenu_items, $id, $default ),
314
				$id
315
			);
316
		}
317
318
		/**
319
		 * Calculates admin settings menu slug.
320
		 * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug.
321
		 *
322
		 * @author Vova Feldman (@svovaf)
323
		 * @since  1.1.3
324
		 *
325
		 * @param string $page
326
		 *
327
		 * @return string
328
		 */
329
		function get_slug( $page = '' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
330
			return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ?
331
				$this->_menu_slug :
332
				$this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) );
333
		}
334
335
		/**
336
		 * @author Vova Feldman (@svovaf)
337
		 * @since  1.1.3
338
		 *
339
		 * @return string
340
		 */
341
		function get_parent_slug() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
342
			return $this->_parent_slug;
343
		}
344
345
		/**
346
		 * @author Vova Feldman (@svovaf)
347
		 * @since  1.1.3
348
		 *
349
		 * @return string
350
		 */
351
		function get_type() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
352
			return $this->_type;
353
		}
354
355
		/**
356
		 * @author Vova Feldman (@svovaf)
357
		 * @since  1.1.3
358
		 *
359
		 * @return bool
360
		 */
361
		function is_cpt() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
362
			return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) ||
363
			         // Back compatibility.
364
			         'cpt' === $this->_type
365
			);
366
		}
367
368
		/**
369
		 * @author Vova Feldman (@svovaf)
370
		 * @since  1.1.3
371
		 *
372
		 * @return string
373
		 */
374
		function get_parent_type() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
375
			return $this->_parent_type;
376
		}
377
378
		/**
379
		 * @author Vova Feldman (@svovaf)
380
		 * @since  1.1.3
381
		 *
382
		 * @return string
383
		 */
384
		function get_raw_slug() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
385
			return $this->_menu_slug;
386
		}
387
388
		/**
389
		 * Get plugin's original menu slug.
390
		 *
391
		 * @author Vova Feldman (@svovaf)
392
		 * @since  1.1.3
393
		 *
394
		 * @return string
395
		 */
396
		function get_original_menu_slug() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
397
			if ( 'cpt' === $this->_type ) {
398
				return add_query_arg( array(
399
					'post_type' => $this->_menu_slug
400
				), 'edit.php' );
401
			}
402
403
			if ( false === strpos( $this->_menu_slug, '.php?' ) ) {
404
				return $this->_menu_slug;
405
			} else {
406
				return $this->_module_unique_affix;
407
			}
408
		}
409
410
		/**
411
		 * @author Vova Feldman (@svovaf)
412
		 * @since  1.1.3
413
		 *
414
		 * @return string
415
		 */
416
		function get_top_level_menu_slug() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
417
			return $this->has_custom_parent() ?
418
				$this->get_parent_slug() :
419
				$this->get_raw_slug();
420
		}
421
422
		/**
423
		 * Is user on plugin's admin activation page.
424
		 *
425
		 * @author Vova Feldman (@svovaf)
426
		 * @since  1.0.8
427
		 *
428
		 * @return bool
429
		 */
430
		function is_main_settings_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
431
			if ( $this->_menu_exists &&
432
			     ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) )
433
			) {
434
				/**
435
				 * Module has a settings menu and the context page is the main settings page, so assume it's in
436
				 * activation (doesn't really check if already opted-in/skipped or not).
437
				 *
438
				 * @since 1.2.2
439
				 */
440
				return true;
441
			}
442
443
			global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
444
			if ( ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && Freemius::is_themes_page() ) {
445
				/**
446
				 * In activation only when show_optin query string param is given.
447
				 *
448
				 * @since 1.2.2
449
				 */
450
				return fs_request_get_bool( $this->_module_unique_affix . '_show_optin' );
451
			}
452
453
			return false;
454
		}
455
456
		#region Submenu Override
457
458
		/**
459
		 * Override submenu's action.
460
		 *
461
		 * @author Vova Feldman (@svovaf)
462
		 * @since  1.1.0
463
		 *
464
		 * @param string   $parent_slug
465
		 * @param string   $menu_slug
466
		 * @param callable $function
467
		 *
468
		 * @return false|string If submenu exist, will return the hook name.
469
		 */
470
		function override_submenu_action( $parent_slug, $menu_slug, $function ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
471
			global $submenu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
472
473
			$menu_slug   = plugin_basename( $menu_slug );
474
			$parent_slug = plugin_basename( $parent_slug );
475
476
			if ( ! isset( $submenu[ $parent_slug ] ) ) {
477
				// Parent menu not exist.
478
				return false;
479
			}
480
481
			$found_submenu_item = false;
482
			foreach ( $submenu[ $parent_slug ] as $submenu_item ) {
483
				if ( $menu_slug === $submenu_item[2] ) {
484
					$found_submenu_item = $submenu_item;
485
					break;
486
				}
487
			}
488
489
			if ( false === $found_submenu_item ) {
490
				// Submenu item not found.
491
				return false;
492
			}
493
494
			// Remove current function.
495
			$hookname = get_plugin_page_hookname( $menu_slug, $parent_slug );
496
			remove_all_actions( $hookname );
497
498
			// Attach new action.
499
			add_action( $hookname, $function );
500
501
			return $hookname;
502
		}
503
504
		#endregion Submenu Override
505
506
		#region Top level menu Override
507
508
		/**
509
		 * Find plugin's admin dashboard main menu item.
510
		 *
511
		 * @author Vova Feldman (@svovaf)
512
		 * @since  1.0.2
513
		 *
514
		 * @return string[]|false
515
		 */
516
		private function find_top_level_menu() {
517
			global $menu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
518
519
			$position   = - 1;
520
			$found_menu = false;
521
522
			$menu_slug = $this->get_raw_slug();
523
524
			$hook_name = get_plugin_page_hookname( $menu_slug, '' );
525
			foreach ( $menu as $pos => $m ) {
526
				if ( $menu_slug === $m[2] ) {
527
					$position   = $pos;
528
					$found_menu = $m;
529
					break;
530
				}
531
			}
532
533
			if ( false === $found_menu ) {
534
				return false;
535
			}
536
537
			return array(
538
				'menu'      => $found_menu,
539
				'position'  => $position,
540
				'hook_name' => $hook_name
541
			);
542
		}
543
544
		/**
545
		 * Find plugin's admin dashboard main submenu item.
546
		 *
547
		 * @author Vova Feldman (@svovaf)
548
		 * @since  1.2.1.6
549
		 *
550
		 * @return array|false
551
		 */
552
		private function find_main_submenu() {
553
			global $submenu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
554
555
			$top_level_menu_slug = $this->get_top_level_menu_slug();
556
557
			if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) {
558
				return false;
559
			}
560
561
			$submenu_slug = $this->get_raw_slug();
562
563
			$position   = - 1;
564
			$found_submenu = false;
565
566
			$hook_name = get_plugin_page_hookname( $submenu_slug, '' );
567
568
			foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) {
569
				if ( $submenu_slug === $sub[2] ) {
570
					$position   = $pos;
571
					$found_submenu = $sub;
572
				}
573
			}
574
575
			if ( false === $found_submenu ) {
576
				return false;
577
			}
578
579
			return array(
580
				'menu'        => $found_submenu,
581
				'parent_slug' => $top_level_menu_slug,
582
				'position'    => $position,
583
				'hook_name'   => $hook_name
584
			);
585
		}
586
587
		/**
588
		 * Remove all sub-menu items.
589
		 *
590
		 * @author Vova Feldman (@svovaf)
591
		 * @since  1.0.7
592
		 *
593
		 * @return bool If submenu with plugin's menu slug was found.
594
		 */
595
		private function remove_all_submenu_items() {
596
			global $submenu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
597
598
			$menu_slug = $this->get_raw_slug();
599
600
			if ( ! isset( $submenu[ $menu_slug ] ) ) {
601
				return false;
602
			}
603
604
			/**
605
			 * This method is NOT executed for WordPress.org themes.
606
			 * Since we maintain only one version of the SDK we added this small
607
			 * hack to avoid the error from Theme Check since it's a false-positive.
608
			 *
609
			 * @author Vova Feldman (@svovaf)
610
			 * @since  1.2.2.7
611
			 */
612
			$submenu_ref               = &$submenu;
613
			$submenu_ref[ $menu_slug ] = array();
614
615
			return true;
616
		}
617
618
		/**
619
		 *
620
		 * @author Vova Feldman (@svovaf)
621
		 * @since  1.0.9
622
		 *
623
         * @param bool $remove_top_level_menu
624
         * 
625
		 * @return false|array[string]mixed
0 ignored issues
show
Documentation introduced by
The doc-type false|array[string]mixed could not be parsed: Expected "]" at position 4, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
626
		 */
627
        function remove_menu_item( $remove_top_level_menu = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
628
            $this->_logger->entrance();
629
630
            // Find main menu item.
631
            $top_level_menu = $this->find_top_level_menu();
632
633
            if ( false === $top_level_menu ) {
634
                return false;
635
            }
636
637
            // Remove it with its actions.
638
            remove_all_actions( $top_level_menu['hook_name'] );
639
640
            // Remove all submenu items.
641
            $this->remove_all_submenu_items();
642
643
            if ( $remove_top_level_menu ) {
644
                global $menu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
645
                unset( $menu[ $top_level_menu['position'] ] );
646
            }
647
648
            return $top_level_menu;
649
        }
650
651
		/**
652
		 * Get module's main admin setting page URL.
653
		 *
654
		 * @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.
655
		 *
656
		 * @author Vova Feldman (@svovaf)
657
		 * @since  1.2.2.7
658
		 *
659
		 * @return string
660
		 */
661
		function main_menu_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
662
			$this->_logger->entrance();
663
664
			if ( $this->_is_top_level ) {
665
				$menu = $this->find_top_level_menu();
666
			} else {
667
				$menu = $this->find_main_submenu();
668
			}
669
670
			$parent_slug = isset( $menu['parent_slug'] ) ?
671
                $menu['parent_slug'] :
672
                'admin.php';
673
674
			return admin_url( $parent_slug . '?page=' . $menu['menu'][2] );
675
		}
676
677
		/**
678
		 * @author Vova Feldman (@svovaf)
679
		 * @since  1.1.4
680
		 *
681
		 * @param callable $function
682
		 *
683
		 * @return false|array[string]mixed
0 ignored issues
show
Documentation introduced by
The doc-type false|array[string]mixed could not be parsed: Expected "]" at position 4, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
684
		 */
685
		function override_menu_item( $function ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
686
			$found_menu = $this->remove_menu_item();
687
688
			if ( false === $found_menu ) {
689
				return false;
690
			}
691
692
			if ( ! $this->is_top_level() || ! $this->is_cpt() ) {
693
				$menu_slug = plugin_basename( $this->get_slug() );
694
695
				$hookname = get_plugin_page_hookname( $menu_slug, '' );
696
697
				// Override menu action.
698
				add_action( $hookname, $function );
699
			} else {
700
				global $menu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
701
702
				// Remove original CPT menu.
703
				unset( $menu[ $found_menu['position'] ] );
704
705
				// Create new top-level menu action.
706
				$hookname = self::add_page(
707
					$found_menu['menu'][3],
708
					$found_menu['menu'][0],
709
					'manage_options',
710
					$this->get_slug(),
711
					$function,
712
					$found_menu['menu'][6],
713
					$found_menu['position']
714
				);
715
			}
716
717
			return $hookname;
718
		}
719
720
		/**
721
		 * Adds a counter to the module's top level menu item.
722
		 *
723
		 * @author Vova Feldman (@svovaf)
724
		 * @since  1.2.1.5
725
		 *
726
		 * @param int    $counter
727
		 * @param string $class
728
		 */
729
		function add_counter_to_menu_item( $counter = 1, $class = '' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
730
			global $menu, $submenu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
731
732
			$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>';
733
734
			/**
735
			 * This method is NOT executed for WordPress.org themes.
736
			 * Since we maintain only one version of the SDK we added this small
737
			 * hack to avoid the error from Theme Check since it's a false-positive.
738
			 *
739
			 * @author Vova Feldman (@svovaf)
740
			 * @since  1.2.2.7
741
			 */
742
			$menu_ref    = &$menu;
743
			$submenu_ref = &$submenu;
744
745
			if ( $this->_is_top_level ) {
746
				// Find main menu item.
747
				$found_menu = $this->find_top_level_menu();
748
749
				if ( false !== $found_menu ) {
750
					// Override menu label.
751
					$menu_ref[ $found_menu['position'] ][0] = sprintf(
752
						$mask,
753
						$found_menu['menu'][0],
754
						$class,
755
						$counter
756
					);
757
				}
758
			} else {
759
				$found_submenu = $this->find_main_submenu();
760
761
				if ( false !== $found_submenu ) {
762
					// Override menu label.
763
					$submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf(
764
						$mask,
765
						$found_submenu['menu'][0],
766
						$class,
767
						$counter
768
					);
769
				}
770
			}
771
		}
772
773
		#endregion Top level menu Override
774
775
		/**
776
		 * Add a top-level menu page.
777
		 *
778
		 * Note for WordPress.org Theme/Plugin reviewer:
779
		 *
780
		 *  This is a replication of `add_menu_page()` to avoid Theme Check warning.
781
		 *
782
		 *  Why?
783
		 *  ====
784
		 *  Freemius is an SDK for plugin and theme developers. Since the core
785
		 *  of the SDK is relevant both for plugins and themes, for obvious reasons,
786
		 *  we only develop and maintain one code base.
787
		 *
788
		 *  This method will not run for wp.org themes (only plugins) since theme
789
		 *  admin settings/options are now only allowed in the customizer.
790
		 *
791
		 *  If you have any questions or need clarifications, please don't hesitate
792
		 *  pinging me on slack, my username is @svovaf.
793
		 *
794
		 * @author Vova Feldman (@svovaf)
795
		 * @since  1.2.2
796
		 *
797
		 * @param string          $page_title The text to be displayed in the title tags of the page when the menu is
798
		 *                                    selected.
799
		 * @param string          $menu_title The text to be used for the menu.
800
		 * @param string          $capability The capability required for this menu to be displayed to the user.
801
		 * @param string          $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
802
		 * @param callable|string $function   The function to be called to output the content for this page.
803
		 * @param string          $icon_url   The URL to the icon to be used for this menu.
804
		 *                                    * Pass a base64-encoded SVG using a data URI, which will be colored to
805
		 *                                    match the color scheme. This should begin with
806
		 *                                    'data:image/svg+xml;base64,'.
807
		 *                                    * Pass the name of a Dashicons helper class to use a font icon,
808
		 *                                    e.g. 'dashicons-chart-pie'.
809
		 *                                    * Pass 'none' to leave div.wp-menu-image empty so an icon can be added
810
		 *                                    via CSS.
811
		 * @param int             $position   The position in the menu order this one should appear.
812
		 *
813
		 * @return string The resulting page's hook_suffix.
814
		 */
815
		static function add_page(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
816
			$page_title,
817
			$menu_title,
818
			$capability,
819
			$menu_slug,
820
			$function = '',
821
			$icon_url = '',
822
			$position = null
823
		) {
824
			$fn = 'add_menu' . '_page';
825
826
			return $fn(
827
				$page_title,
828
				$menu_title,
829
				$capability,
830
				$menu_slug,
831
				$function,
832
				$icon_url,
833
				$position
834
			);
835
		}
836
837
        /**
838
         * Add page and update menu instance settings.
839
         *
840
         * @author Vova Feldman (@svovaf)
841
         * @since  2.0.0
842
         *
843
         * @param string          $page_title
844
         * @param string          $menu_title
845
         * @param string          $capability
846
         * @param string          $menu_slug
847
         * @param callable|string $function
848
         * @param string          $icon_url
849
         * @param int|null        $position
850
         *
851
         * @return string
852
         */
853
		function add_page_and_update(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
854
            $page_title,
855
            $menu_title,
856
            $capability,
857
            $menu_slug,
858
            $function = '',
859
            $icon_url = '',
860
            $position = null
861
        ) {
862
            $this->_menu_slug           = $menu_slug;
863
            $this->_is_top_level        = true;
864
            $this->_menu_exists         = true;
865
            $this->_network_menu_exists = true;
866
867
            return self::add_page(
868
                $page_title,
869
                $menu_title,
870
                $capability,
871
                $menu_slug,
872
                $function,
873
                $icon_url,
874
                $position
875
            );
876
        }
877
878
		/**
879
		 * Add a submenu page.
880
		 *
881
		 * Note for WordPress.org Theme/Plugin reviewer:
882
		 *
883
		 *  This is a replication of `add_submenu_page()` to avoid Theme Check warning.
884
		 *
885
		 *  Why?
886
		 *  ====
887
		 *  Freemius is an SDK for plugin and theme developers. Since the core
888
		 *  of the SDK is relevant both for plugins and themes, for obvious reasons,
889
		 *  we only develop and maintain one code base.
890
		 *
891
		 *  This method will not run for wp.org themes (only plugins) since theme
892
		 *  admin settings/options are now only allowed in the customizer.
893
		 *
894
		 *  If you have any questions or need clarifications, please don't hesitate
895
		 *  pinging me on slack, my username is @svovaf.
896
		 *
897
		 * @author Vova Feldman (@svovaf)
898
		 * @since  1.2.2
899
		 *
900
		 * @param string          $parent_slug The slug name for the parent menu (or the file name of a standard
901
		 *                                     WordPress admin page).
902
		 * @param string          $page_title  The text to be displayed in the title tags of the page when the menu is
903
		 *                                     selected.
904
		 * @param string          $menu_title  The text to be used for the menu.
905
		 * @param string          $capability  The capability required for this menu to be displayed to the user.
906
		 * @param string          $menu_slug   The slug name to refer to this menu by (should be unique for this menu).
907
		 * @param callable|string $function    The function to be called to output the content for this page.
908
		 *
909
		 * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability
910
		 *                      required.
911
		 */
912
		static function add_subpage(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
913
			$parent_slug,
914
			$page_title,
915
			$menu_title,
916
			$capability,
917
			$menu_slug,
918
			$function = ''
919
		) {
920
			$fn = 'add_submenu' . '_page';
921
922
			return $fn( $parent_slug,
923
				$page_title,
924
				$menu_title,
925
				$capability,
926
				$menu_slug,
927
				$function
928
			);
929
		}
930
931
        /**
932
         * Add sub page and update menu instance settings.
933
         *
934
         * @author Vova Feldman (@svovaf)
935
         * @since  2.0.0
936
         *
937
         * @param string          $parent_slug
938
         * @param string          $page_title
939
         * @param string          $menu_title
940
         * @param string          $capability
941
         * @param string          $menu_slug
942
         * @param callable|string $function
943
         *
944
         * @return string
945
         */
946
        function add_subpage_and_update(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
947
            $parent_slug,
948
            $page_title,
949
            $menu_title,
950
            $capability,
951
            $menu_slug,
952
            $function = ''
953
        ) {
954
            $this->_menu_slug           = $menu_slug;
955
            $this->_parent_slug         = $parent_slug;
956
            $this->_is_top_level        = false;
957
            $this->_menu_exists         = true;
958
            $this->_network_menu_exists = true;
959
960
            return self::add_subpage(
961
                $parent_slug,
962
                $page_title,
963
                $menu_title,
964
                $capability,
965
                $menu_slug,
966
                $function
967
            );
968
        }
969
	}