| 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 |
} |
| 1026 |
} |