Admin Menu Editor - Version 1.8.1

Version Description

  • Added a workaround for a buggy "defer_parsing_of_js" code snippet that some users have added to their functions.php. This snippet produces invalid HTML code, which used to break the menu editor.
  • Fixed a PHP warning that appeared when using this plugin together with WooCommerce or YITH WooCommerce Gift Cards and running PHP 7.1.
  • Minor performance improvements.
  • Tested with WP 4.8.3 and 4.9.
Download this release

Release Info

Developer whiteshadow
Plugin Icon 128x128 Admin Menu Editor
Version 1.8.1
Comparing to
See all releases

Code changes from version 1.8 to 1.8.1

css/menu-editor.css CHANGED
@@ -759,6 +759,8 @@ select.ws_dropdown optgroup option {
759
 
760
  .ame-color-option {
761
  padding: 10px 0; }
 
 
762
 
763
  .ame-advanced-menu-color {
764
  display: none; }
759
 
760
  .ame-color-option {
761
  padding: 10px 0; }
762
+ .ame-color-option .wp-picker-container {
763
+ display: inline-block; }
764
 
765
  .ame-advanced-menu-color {
766
  display: none; }
css/menu-editor.scss CHANGED
@@ -1031,6 +1031,10 @@ $activeToolTabBackground: #FDFDFD;
1031
 
1032
  .ame-color-option {
1033
  padding: 10px 0;
 
 
 
 
1034
  }
1035
 
1036
  .ame-advanced-menu-color {
1031
 
1032
  .ame-color-option {
1033
  padding: 10px 0;
1034
+
1035
+ .wp-picker-container {
1036
+ display: inline-block;
1037
+ }
1038
  }
1039
 
1040
  .ame-advanced-menu-color {
includes/editor-page.php CHANGED
@@ -72,9 +72,7 @@ if ( !apply_filters('admin_menu_editor_is_pro', false) ){
72
 
73
  <?php
74
  if ( !empty($_GET['message']) ){
75
- if ( intval($_GET['message']) == 1 ){
76
- echo '<div id="message" class="updated notice is-dismissible"><p><strong>Settings saved.</strong></p></div>';
77
- } elseif ( intval($_GET['message']) == 2 ) {
78
  echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
79
  }
80
  }
@@ -250,9 +248,12 @@ function ame_output_sort_buttons($icons) {
250
  <div class="ws_basic_container">
251
 
252
  <div class="ws_main_container" id="ws_editor_sidebar">
253
- <form method="post" action="<?php echo admin_url('options-general.php?page=menu_editor&noheader=1'); ?>" id='ws_main_form' name='ws_main_form'>
254
  <?php wp_nonce_field('menu-editor-form'); ?>
255
  <input type="hidden" name="action" value="save_menu">
 
 
 
256
  <input type="hidden" name="data" id="ws_data" value="">
257
  <input type="hidden" name="data_length" id="ws_data_length" value="">
258
  <input type="hidden" name="selected_actor" id="ws_selected_actor" value="">
@@ -507,7 +508,7 @@ function ame_output_sort_buttons($icons) {
507
  }
508
 
509
  $defaultIconImages = array(
510
- 'images/generic.png',
511
  );
512
  foreach($defaultIconImages as $icon) {
513
  printf(
72
 
73
  <?php
74
  if ( !empty($_GET['message']) ){
75
+ if ( intval($_GET['message']) == 2 ) {
 
 
76
  echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
77
  }
78
  }
248
  <div class="ws_basic_container">
249
 
250
  <div class="ws_main_container" id="ws_editor_sidebar">
251
+ <form method="post" action="<?php echo esc_attr(add_query_arg('noheader', '1', $editor_data['current_tab_url'])); ?>" id='ws_main_form' name='ws_main_form'>
252
  <?php wp_nonce_field('menu-editor-form'); ?>
253
  <input type="hidden" name="action" value="save_menu">
254
+ <?php
255
+ printf('<input type="hidden" name="config_id" value="%s">', esc_attr($editor_data['menu_config_id']));
256
+ ?>
257
  <input type="hidden" name="data" id="ws_data" value="">
258
  <input type="hidden" name="data_length" id="ws_data_length" value="">
259
  <input type="hidden" name="selected_actor" id="ws_selected_actor" value="">
508
  }
509
 
510
  $defaultIconImages = array(
511
+ admin_url('images/generic.png'),
512
  );
513
  foreach($defaultIconImages as $icon) {
514
  printf(
includes/menu-editor-core.php CHANGED
@@ -25,6 +25,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
25
  const VERBOSITY_NORMAL = 2;
26
  const VERBOSITY_VERBOSE = 5;
27
 
 
 
 
28
  /**
29
  * @var string The heading tag to use for admin pages.
30
  */
@@ -80,6 +83,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
80
  private $relative_template_order = array();
81
 
82
  private $cached_custom_menu = null; //Cached, non-merged version of the custom menu. Used by load_custom_menu().
 
83
  private $cached_virtual_caps = null;//List of virtual caps. Used by get_virtual_caps().
84
 
85
  private $cached_user_caps = array(); //A cache of the current user's capabilities. Used only in very specific places.
@@ -121,6 +125,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
121
  'hide_advanced_settings' => true,
122
  'show_extra_icons' => false,
123
  'custom_menu' => null,
 
124
  'first_install_time' => null,
125
  'display_survey_notice' => true,
126
  'plugin_db_version' => 0,
@@ -175,7 +180,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
175
  //WP 4.3+ uses H1 headings for admin pages. Older versions use H2 instead.
176
  self::$admin_heading_tag = version_compare($GLOBALS['wp_version'], '4.3', '<') ? 'h2' : 'h1';
177
 
178
- $this->settings_link = 'options-general.php?page=menu_editor';
179
 
180
  $this->magic_hooks = true;
181
  //Run our hooks last (almost). Priority is less than PHP_INT_MAX mostly for defensive programming purposes.
@@ -325,7 +330,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
325
  }
326
 
327
  //Set up the tabs for the menu editor page.
328
- $this->tabs = apply_filters('admin_menu_editor-tabs', array( 'editor' => 'Admin Menu', ));
 
 
 
 
 
329
  //The "Settings" tab is always last.
330
  $this->tabs['settings'] = 'Settings';
331
  }
@@ -361,7 +371,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
361
  //The only way we can see the changes made by the second hook is to do the same thing.
362
  static $firstRunSkipped = false;
363
  if ( !$firstRunSkipped && class_exists('Flow') ) {
364
- add_action('admin_menu', array($this, 'hook_admin_menu'), $this->magic_hook_priority + 1);
365
  $firstRunSkipped = true;
366
  return;
367
  }
@@ -378,13 +388,17 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
378
  $this->log_security_note('Current user can edit the admin menu.');
379
 
380
  //Determine the current menu editor page tab.
381
- $this->current_tab = isset($this->get['sub_section']) ? strval($this->get['sub_section']) : 'editor';
 
382
  $tab_title = '';
383
  if ($this->current_tab !== 'editor' && isset($this->tabs[$this->current_tab])) {
384
  $tab_title = ' - ' . $this->tabs[$this->current_tab];
385
  }
386
 
387
- $page = add_options_page(
 
 
 
388
  apply_filters('admin_menu_editor-self_page_title', 'Menu Editor') . $tab_title,
389
  apply_filters('admin_menu_editor-self_menu_title', 'Menu Editor'),
390
  apply_filters('admin_menu_editor-capability', 'manage_options'),
@@ -408,7 +422,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
408
  add_action("admin_print_scripts-$page", array($this, 'remove_ultimate_tinymce_qtags'));
409
 
410
  //Make a placeholder for our screen options (hacky)
411
- add_meta_box("ws-ame-screen-options", "[AME placeholder]", '__return_false', $page);
 
 
 
 
412
  }
413
 
414
  //Compatibility fix for the WooCommerce order count bubble. Must be run before storing or processing $submenu.
@@ -444,6 +462,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
444
 
445
  //Add extra templates that are not part of the normal menu.
446
  $this->item_templates = $this->add_special_templates($this->item_templates);
 
447
 
448
  //Is there a custom menu to use?
449
  $custom_menu = $this->load_custom_menu();
@@ -740,6 +759,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
740
  //it tracks *all* DOM changes using MutationObserver.
741
  remove_action('admin_print_scripts', 'print_emoji_detection_script');
742
 
 
 
 
 
743
  $this->register_base_dependencies();
744
 
745
  //Move admin notices (e.g. "Settings saved") below editor tabs.
@@ -827,7 +850,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
827
  'blankMenuItem' => ameMenuItem::blank_menu(),
828
  'itemTemplates' => $this->item_templates,
829
  'customItemTemplate' => array(
830
- 'name' => '< Custom >',
831
  'defaults' => ameMenuItem::custom_item_defaults(),
832
  ),
833
 
@@ -1019,29 +1042,45 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1019
  * Set and save a new custom menu for the current site.
1020
  *
1021
  * @param array|null $custom_menu
 
1022
  */
1023
- function set_custom_menu($custom_menu) {
 
 
 
 
1024
  $custom_menu = apply_filters('ame_pre_set_custom_menu', $custom_menu);
1025
 
1026
- $previous_custom_menu = $this->load_custom_menu();
1027
  $this->update_wpml_strings($previous_custom_menu, $custom_menu);
1028
 
 
 
 
 
1029
  if ( !empty($custom_menu) && $this->options['compress_custom_menu'] ) {
1030
  $custom_menu = ameMenu::compress($custom_menu);
1031
  }
1032
 
1033
- if ( $this->should_use_site_specific_menu() ) {
1034
  $site_specific_options = get_option($this->option_name);
1035
  if ( !is_array($site_specific_options) ) {
1036
  $site_specific_options = array();
1037
  }
1038
  $site_specific_options['custom_menu'] = $custom_menu;
1039
  update_option($this->option_name, $site_specific_options);
1040
- } else {
1041
  $this->options['custom_menu'] = $custom_menu;
1042
  $this->save_options();
 
 
 
 
 
 
1043
  }
1044
 
 
1045
  $this->cached_custom_menu = null;
1046
  $this->cached_virtual_caps = null;
1047
  $this->cached_user_caps = array();
@@ -1050,14 +1089,26 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1050
  /**
1051
  * Load the current custom menu for this site, if any.
1052
  *
 
1053
  * @return array|null Either a menu in the internal format, or NULL if there is no custom menu available.
1054
  */
1055
- public function load_custom_menu() {
1056
- if ( $this->cached_custom_menu !== null ) {
 
 
 
 
1057
  return $this->cached_custom_menu;
1058
  }
1059
 
1060
- if ( $this->should_use_site_specific_menu() ) {
 
 
 
 
 
 
 
1061
  $site_specific_options = get_option($this->option_name, null);
1062
  if ( is_array($site_specific_options) && isset($site_specific_options['custom_menu']) ) {
1063
  $this->cached_custom_menu = ameMenu::load_array($site_specific_options['custom_menu']);
@@ -1072,6 +1123,23 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1072
  return $this->cached_custom_menu;
1073
  }
1074
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1075
  /**
1076
  * Determine if we should use a site-specific admin menu configuration
1077
  * for the current site, or fall back to the global config.
@@ -1378,7 +1446,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1378
  unset($following_separator_position[$previous_item]);
1379
  }
1380
  }
1381
- $entry['position'] = $entry['position'] + 0.01;
1382
  } else if ( $previous_item === '' ) {
1383
  //Empty string = this was originally the first item.
1384
  $entry['position'] = -1;
@@ -1628,7 +1696,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1628
  $item['page_title'],
1629
  $item['css_class'],
1630
  $item['hookname'], //ID
1631
- $item['icon_url']
1632
  );
1633
 
1634
  return $wp_item;
@@ -1825,6 +1893,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1825
  return $item;
1826
  }
1827
 
 
1828
  $item = apply_filters('custom_admin_menu_capability', $item);
1829
 
1830
  $item['access_check_log'][] = '-----';
@@ -1960,7 +2029,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1960
  $this->display_plugin_settings_ui();
1961
  } else if ( $this->current_tab == 'generate-menu-dashicons' ) {
1962
  require dirname(__FILE__) . '/generate-menu-dashicons.php';
1963
- } else if ( $this->current_tab === 'editor' ) {
 
 
1964
  $this->display_editor_ui();
1965
  } else {
1966
  do_action('admin_menu_editor-section-' . $this->current_tab);
@@ -1971,6 +2042,67 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1971
  do_action('admin_menu_editor-footer-' . $this->current_tab, $action);
1972
  }
1973
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1974
  private function handle_form_submission($post, $action = '') {
1975
  if ( $action == 'save_menu' ) {
1976
  //Save the admin menu configuration.
@@ -2014,8 +2146,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2014
  //Remember if the user has changed any menu icons to different Dashicons.
2015
  $menu['has_modified_dashicons'] = ameModifiedIconDetector::detect($menu);
2016
 
 
 
 
 
 
 
2017
  //Save the custom menu
2018
- $this->set_custom_menu($menu);
2019
 
2020
  //Redirect back to the editor and display the success message.
2021
  $query = array('message' => 1);
@@ -2144,7 +2282,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2144
  );
2145
 
2146
  $this->save_options();
2147
- wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
2148
  }
2149
  }
2150
 
@@ -2155,9 +2293,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2155
  'images_url' => plugins_url('images', $this->plugin_file),
2156
  'hide_advanced_settings' => $this->options['hide_advanced_settings'],
2157
  'show_extra_icons' => $this->options['show_extra_icons'],
 
2158
  'settings_page_url' => $this->get_settings_page_url(),
2159
  'show_deprecated_hide_button' => $this->options['show_deprecated_hide_button'],
2160
  'dashicons_available' => wp_style_is('dashicons', 'done'),
 
2161
  );
2162
 
2163
  //Build a tree struct. for the default menu
@@ -2257,6 +2397,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2257
  );
2258
 
2259
  do_action('admin_menu_editor-display_tabs');
 
 
 
 
 
2260
  }
2261
 
2262
  public function display_settings_page_footer() {
@@ -2271,7 +2416,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2271
  foreach($this->tabs as $slug => $title) {
2272
  printf(
2273
  '<a href="%s" id="%s" class="nav-tab%s">%s</a>',
2274
- esc_attr(add_query_arg('sub_section', $slug, admin_url($this->settings_link))),
2275
  esc_attr('ws_ame_' . $slug . '_tab'),
2276
  $slug === $this->current_tab ? ' nav-tab-active' : '',
2277
  $title
@@ -2298,13 +2443,27 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2298
  require dirname(__FILE__) . '/settings-page.php';
2299
  }
2300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2301
  /**
2302
  * Get the fully qualified URL of the "Settings" sub-section of our plugin page.
2303
  *
2304
  * @return string
2305
  */
2306
  private function get_settings_page_url() {
2307
- return add_query_arg('sub_section', 'settings', admin_url($this->settings_link));
2308
  }
2309
 
2310
  /**
@@ -2312,8 +2471,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2312
  *
2313
  * @return bool
2314
  */
2315
- protected function is_editor_page() {
2316
- return $this->is_tab_open('editor');
2317
  }
2318
 
2319
  /**
@@ -2336,46 +2495,73 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2336
  && ($this->current_tab === $tab_slug)
2337
  && isset($this->get['page']) && ($this->get['page'] == 'menu_editor');
2338
  }
2339
-
2340
  /**
2341
- * Generate a list of "virtual" capabilities that should be granted to certain roles.
2342
- *
2343
- * This is based on grant_access settings for the current custom menu and enables
2344
- * selected roles and users to access menu items that they ordinarily would not
2345
- * be able to.
2346
  *
2347
- * @uses self::get_virtual_caps_for() to actually generate the caps.
2348
  * @uses self::$cached_virtual_caps to cache the generated list of caps.
2349
  *
 
2350
  * @return array A list of capability => [role1 => true, ... roleN => true] assignments.
2351
  */
2352
- function get_virtual_caps() {
 
 
 
 
2353
  if ( $this->cached_virtual_caps !== null ) {
2354
- return $this->cached_virtual_caps;
2355
  }
2356
 
2357
- $caps = array();
2358
  $custom_menu = $this->load_custom_menu();
2359
  if ( $custom_menu === null ){
2360
- return $caps;
2361
  }
2362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2363
  //Include directly granted capabilities.
 
2364
  if ( !empty($custom_menu['granted_capabilities']) ) {
2365
  foreach ($custom_menu['granted_capabilities'] as $actor => $capabilities) {
2366
  foreach ($capabilities as $capability => $allow) {
2367
- $caps[$actor][$capability] = (bool)(is_array($allow) ? $allow[0] : $allow);
2368
  }
2369
  }
2370
  }
2371
 
2372
- //grant_access settings on individual items have precedence.
 
2373
  foreach($custom_menu['tree'] as $item) {
2374
- $caps = self::array_replace_recursive($caps, $this->get_virtual_caps_for($item));
2375
  }
2376
 
2377
- $this->cached_virtual_caps = $caps;
2378
- return $caps;
 
 
 
 
 
2379
  }
2380
 
2381
  private function get_virtual_caps_for($item) {
@@ -2425,6 +2611,22 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2425
  } elseif ( $capability === 'delete_site' ) {
2426
  return 'manage_options';
2427
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2428
  return $capability;
2429
  }
2430
 
@@ -2441,6 +2643,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2441
 
2442
  if ($this->options['menu_config_scope'] === 'site') {
2443
  $this->cached_custom_menu = null;
 
2444
  }
2445
  }
2446
 
@@ -2544,6 +2747,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2544
  'sort_column' => 'post_modified',
2545
  'sort_order' => 'DESC',
2546
  'hierarchical' => false,
 
2547
  'number' => 50, //Semi-arbitrary. We do need a limit - some users could have thousands of pages.
2548
  ));
2549
  /** @var WP_Post[] $pages */
25
  const VERBOSITY_NORMAL = 2;
26
  const VERBOSITY_VERBOSE = 5;
27
 
28
+ const DIRECTLY_GRANTED_VIRTUAL_CAPS = 2;
29
+ const ALL_VIRTUAL_CAPS = 3;
30
+
31
  /**
32
  * @var string The heading tag to use for admin pages.
33
  */
83
  private $relative_template_order = array();
84
 
85
  private $cached_custom_menu = null; //Cached, non-merged version of the custom menu. Used by load_custom_menu().
86
+ private $loaded_menu_config_id = null;
87
  private $cached_virtual_caps = null;//List of virtual caps. Used by get_virtual_caps().
88
 
89
  private $cached_user_caps = array(); //A cache of the current user's capabilities. Used only in very specific places.
125
  'hide_advanced_settings' => true,
126
  'show_extra_icons' => false,
127
  'custom_menu' => null,
128
+ 'custom_network_menu' => null,
129
  'first_install_time' => null,
130
  'display_survey_notice' => true,
131
  'plugin_db_version' => 0,
180
  //WP 4.3+ uses H1 headings for admin pages. Older versions use H2 instead.
181
  self::$admin_heading_tag = version_compare($GLOBALS['wp_version'], '4.3', '<') ? 'h2' : 'h1';
182
 
183
+ $this->settings_link = (is_network_admin() ? 'settings.php' : 'options-general.php') . '?page=menu_editor';
184
 
185
  $this->magic_hooks = true;
186
  //Run our hooks last (almost). Priority is less than PHP_INT_MAX mostly for defensive programming purposes.
330
  }
331
 
332
  //Set up the tabs for the menu editor page.
333
+ $firstTabs = array('editor' => 'Admin Menu');
334
+ if ( is_network_admin() ) {
335
+ //TODO: This could be in extras.php
336
+ $firstTabs = array('network-admin-menu' => 'Network Admin Menu');
337
+ }
338
+ $this->tabs = apply_filters('admin_menu_editor-tabs', $firstTabs);
339
  //The "Settings" tab is always last.
340
  $this->tabs['settings'] = 'Settings';
341
  }
371
  //The only way we can see the changes made by the second hook is to do the same thing.
372
  static $firstRunSkipped = false;
373
  if ( !$firstRunSkipped && class_exists('Flow') ) {
374
+ add_action(current_filter(), array($this, 'hook_admin_menu'), $this->magic_hook_priority + 1);
375
  $firstRunSkipped = true;
376
  return;
377
  }
388
  $this->log_security_note('Current user can edit the admin menu.');
389
 
390
  //Determine the current menu editor page tab.
391
+ reset($this->tabs);
392
+ $this->current_tab = isset($this->get['sub_section']) ? strval($this->get['sub_section']) : key($this->tabs);
393
  $tab_title = '';
394
  if ($this->current_tab !== 'editor' && isset($this->tabs[$this->current_tab])) {
395
  $tab_title = ' - ' . $this->tabs[$this->current_tab];
396
  }
397
 
398
+ $parent_slug = is_network_admin() ? 'settings.php' : 'options-general.php';
399
+
400
+ $page = add_submenu_page(
401
+ $parent_slug,
402
  apply_filters('admin_menu_editor-self_page_title', 'Menu Editor') . $tab_title,
403
  apply_filters('admin_menu_editor-self_menu_title', 'Menu Editor'),
404
  apply_filters('admin_menu_editor-capability', 'manage_options'),
422
  add_action("admin_print_scripts-$page", array($this, 'remove_ultimate_tinymce_qtags'));
423
 
424
  //Make a placeholder for our screen options (hacky)
425
+ $screen_hook_name = $page;
426
+ if ( is_network_admin() ) {
427
+ $screen_hook_name .= '-network';
428
+ }
429
+ add_meta_box("ws-ame-screen-options", "[AME placeholder]", '__return_false', $screen_hook_name);
430
  }
431
 
432
  //Compatibility fix for the WooCommerce order count bubble. Must be run before storing or processing $submenu.
462
 
463
  //Add extra templates that are not part of the normal menu.
464
  $this->item_templates = $this->add_special_templates($this->item_templates);
465
+ //TODO: It would be nice to add the "Delete Site" item on multisite when on the main site.
466
 
467
  //Is there a custom menu to use?
468
  $custom_menu = $this->load_custom_menu();
759
  //it tracks *all* DOM changes using MutationObserver.
760
  remove_action('admin_print_scripts', 'print_emoji_detection_script');
761
 
762
+ //Workaround: Suppress a buggy "lets add a 'defer' attribute to all <script> tags" filter.
763
+ //It's been going around the web and breaking AME installations by producing invalid HTML.
764
+ remove_filter('clean_url', 'defer_parsing_of_js', 11);
765
+
766
  $this->register_base_dependencies();
767
 
768
  //Move admin notices (e.g. "Settings saved") below editor tabs.
850
  'blankMenuItem' => ameMenuItem::blank_menu(),
851
  'itemTemplates' => $this->item_templates,
852
  'customItemTemplate' => array(
853
+ 'name' => '< Custom URL >',
854
  'defaults' => ameMenuItem::custom_item_defaults(),
855
  ),
856
 
1042
  * Set and save a new custom menu for the current site.
1043
  *
1044
  * @param array|null $custom_menu
1045
+ * @param string|null $config_id Supported values: 'network-admin', 'global' or 'site'
1046
  */
1047
+ function set_custom_menu($custom_menu, $config_id = null) {
1048
+ if ( $config_id === null ) {
1049
+ $config_id = $this->guess_menu_config_id();
1050
+ }
1051
+
1052
  $custom_menu = apply_filters('ame_pre_set_custom_menu', $custom_menu);
1053
 
1054
+ $previous_custom_menu = $this->load_custom_menu($config_id);
1055
  $this->update_wpml_strings($previous_custom_menu, $custom_menu);
1056
 
1057
+ if ( !empty($custom_menu) ) {
1058
+ $custom_menu['prebuilt_virtual_caps'] = $this->build_virtual_capability_list($custom_menu);
1059
+ }
1060
+
1061
  if ( !empty($custom_menu) && $this->options['compress_custom_menu'] ) {
1062
  $custom_menu = ameMenu::compress($custom_menu);
1063
  }
1064
 
1065
+ if ($config_id === 'site') {
1066
  $site_specific_options = get_option($this->option_name);
1067
  if ( !is_array($site_specific_options) ) {
1068
  $site_specific_options = array();
1069
  }
1070
  $site_specific_options['custom_menu'] = $custom_menu;
1071
  update_option($this->option_name, $site_specific_options);
1072
+ } else if ($config_id === 'global') {
1073
  $this->options['custom_menu'] = $custom_menu;
1074
  $this->save_options();
1075
+
1076
+ } else if ($config_id === 'network-admin' ) {
1077
+ $this->options['custom_network_menu'] = $custom_menu;
1078
+ $this->save_options();
1079
+ } else {
1080
+ throw new LogicException(sprintf('Invalid menu configuration ID: "%s"', $config_id));
1081
  }
1082
 
1083
+ $this->loaded_menu_config_id = null;
1084
  $this->cached_custom_menu = null;
1085
  $this->cached_virtual_caps = null;
1086
  $this->cached_user_caps = array();
1089
  /**
1090
  * Load the current custom menu for this site, if any.
1091
  *
1092
+ * @param null $config_id
1093
  * @return array|null Either a menu in the internal format, or NULL if there is no custom menu available.
1094
  */
1095
+ public function load_custom_menu($config_id = null) {
1096
+ if ( $config_id === null ) {
1097
+ $config_id = $this->guess_menu_config_id();
1098
+ }
1099
+
1100
+ if ( ($this->cached_custom_menu !== null) && ($this->loaded_menu_config_id === $config_id) ) {
1101
  return $this->cached_custom_menu;
1102
  }
1103
 
1104
+ $this->loaded_menu_config_id = $config_id;
1105
+
1106
+ if ( $config_id === 'network-admin' ) {
1107
+ if ( empty($this->options['custom_network_menu']) ) {
1108
+ return null;
1109
+ }
1110
+ $this->cached_custom_menu = ameMenu::load_array($this->options['custom_network_menu']);
1111
+ } else if ( $config_id === 'site' ) {
1112
  $site_specific_options = get_option($this->option_name, null);
1113
  if ( is_array($site_specific_options) && isset($site_specific_options['custom_menu']) ) {
1114
  $this->cached_custom_menu = ameMenu::load_array($site_specific_options['custom_menu']);
1123
  return $this->cached_custom_menu;
1124
  }
1125
 
1126
+ private function guess_menu_config_id() {
1127
+ if ( is_network_admin() ) {
1128
+ return 'network-admin';
1129
+ } elseif ( $this->should_use_site_specific_menu() ) {
1130
+ return 'site';
1131
+ } else {
1132
+ return 'global';
1133
+ }
1134
+ }
1135
+
1136
+ /**
1137
+ * @return string|null
1138
+ */
1139
+ public function get_loaded_menu_config_id() {
1140
+ return $this->loaded_menu_config_id;
1141
+ }
1142
+
1143
  /**
1144
  * Determine if we should use a site-specific admin menu configuration
1145
  * for the current site, or fall back to the global config.
1446
  unset($following_separator_position[$previous_item]);
1447
  }
1448
  }
1449
+ $entry['position'] = strval(floatval($entry['position']) + 0.01);
1450
  } else if ( $previous_item === '' ) {
1451
  //Empty string = this was originally the first item.
1452
  $entry['position'] = -1;
1696
  $item['page_title'],
1697
  $item['css_class'],
1698
  $item['hookname'], //ID
1699
+ isset($item['wp_icon_url']) ? $item['wp_icon_url'] : $item['icon_url'],
1700
  );
1701
 
1702
  return $wp_item;
1893
  return $item;
1894
  }
1895
 
1896
+ //TODO: A direct call to apply_custom_access would be faster.
1897
  $item = apply_filters('custom_admin_menu_capability', $item);
1898
 
1899
  $item['access_check_log'][] = '-----';
2029
  $this->display_plugin_settings_ui();
2030
  } else if ( $this->current_tab == 'generate-menu-dashicons' ) {
2031
  require dirname(__FILE__) . '/generate-menu-dashicons.php';
2032
+ } else if ( $this->current_tab === 'repair-database' ) {
2033
+ $this->repair_database();
2034
+ } else if ( $this->is_editor_page() ) {
2035
  $this->display_editor_ui();
2036
  } else {
2037
  do_action('admin_menu_editor-section-' . $this->current_tab);
2042
  do_action('admin_menu_editor-footer-' . $this->current_tab, $action);
2043
  }
2044
 
2045
+ private function repair_database() {
2046
+ global $wpdb; /** @var wpdb $wpdb */
2047
+
2048
+ if ( !is_multisite() ) {
2049
+ echo 'This is not Multisite. The "repair" function does not apply to your site.';
2050
+ return;
2051
+ }
2052
+
2053
+ echo '<div class="wrap"><h1>Repairing database...</h1><p></p>';
2054
+
2055
+ $options_to_repair = array(
2056
+ $this->option_name,
2057
+ 'wsh_license_manager-admin-menu-editor-pro',
2058
+ 'ws_abe_admin_bar_nodes',
2059
+ 'ws_abe_admin_bar_settings',
2060
+ );
2061
+
2062
+ echo "Repair {$wpdb->sitemeta}<br>";
2063
+ $wpdb->query('REPAIR TABLE ' . $wpdb->sitemeta);
2064
+
2065
+ echo "Lock {$wpdb->sitemeta}<br>";
2066
+ $wpdb->query('LOCK TABLES ' . $wpdb->sitemeta);
2067
+
2068
+ foreach($options_to_repair as $option) {
2069
+ if ( empty($option) ) {
2070
+ continue; //Sanity check.
2071
+ }
2072
+
2073
+ echo "Fetch option {$option}<br>";
2074
+ $row = $wpdb->get_row($wpdb->prepare(
2075
+ "SELECT * FROM {$wpdb->sitemeta} WHERE meta_key = %s LIMIT 1",
2076
+ $option
2077
+ ));
2078
+
2079
+ if ( empty($row) || empty($row->site_id) ) {
2080
+ echo "Option doesn't exist, skipping it.<br>";
2081
+ continue;
2082
+ }
2083
+
2084
+ echo "Delete all rows where meta_key = {$option}<br>";
2085
+ $wpdb->delete($wpdb->sitemeta, array('meta_key' => $option), '%s');
2086
+
2087
+ echo "Recreate the first copy of {$option}<br>";
2088
+ $wpdb->insert(
2089
+ $wpdb->sitemeta,
2090
+ array(
2091
+ 'site_id' => $row->site_id,
2092
+ 'meta_key' => $option,
2093
+ 'meta_value' => $row->meta_value,
2094
+ ),
2095
+ array('%d', '%s', '%s')
2096
+ );
2097
+ }
2098
+
2099
+ echo "Unlock {$wpdb->sitemeta}<br>";
2100
+ $wpdb->query('UNLOCK TABLES');
2101
+
2102
+ echo "Done.<br>";
2103
+ echo '<div>';
2104
+ }
2105
+
2106
  private function handle_form_submission($post, $action = '') {
2107
  if ( $action == 'save_menu' ) {
2108
  //Save the admin menu configuration.
2146
  //Remember if the user has changed any menu icons to different Dashicons.
2147
  $menu['has_modified_dashicons'] = ameModifiedIconDetector::detect($menu);
2148
 
2149
+ //Which menu configuration are we changing?
2150
+ $config_id = isset($post['config_id']) ? $post['config_id'] : null;
2151
+ if ( !in_array($config_id, array('site', 'global', 'network-admin')) ) {
2152
+ $config_id = $this->get_loaded_menu_config_id();
2153
+ }
2154
+
2155
  //Save the custom menu
2156
+ $this->set_custom_menu($menu, $config_id);
2157
 
2158
  //Redirect back to the editor and display the success message.
2159
  $query = array('message' => 1);
2282
  );
2283
 
2284
  $this->save_options();
2285
+ wp_redirect(add_query_arg('message', 1, $this->get_settings_page_url()));
2286
  }
2287
  }
2288
 
2293
  'images_url' => plugins_url('images', $this->plugin_file),
2294
  'hide_advanced_settings' => $this->options['hide_advanced_settings'],
2295
  'show_extra_icons' => $this->options['show_extra_icons'],
2296
+ 'current_tab_url' => $this->get_plugin_page_url(array('sub_section' => $this->current_tab)),
2297
  'settings_page_url' => $this->get_settings_page_url(),
2298
  'show_deprecated_hide_button' => $this->options['show_deprecated_hide_button'],
2299
  'dashicons_available' => wp_style_is('dashicons', 'done'),
2300
+ 'menu_config_id' => $this->get_loaded_menu_config_id(),
2301
  );
2302
 
2303
  //Build a tree struct. for the default menu
2397
  );
2398
 
2399
  do_action('admin_menu_editor-display_tabs');
2400
+
2401
+ if ( isset($_GET['message']) && (intval($_GET['message']) === 1) ) {
2402
+ add_settings_error('ame-settings-page', 'settings_updated', __('Settings saved.'), 'updated');
2403
+ }
2404
+ settings_errors('ame-settings-page');
2405
  }
2406
 
2407
  public function display_settings_page_footer() {
2416
  foreach($this->tabs as $slug => $title) {
2417
  printf(
2418
  '<a href="%s" id="%s" class="nav-tab%s">%s</a>',
2419
+ esc_attr(add_query_arg('sub_section', $slug, self_admin_url($this->settings_link))),
2420
  esc_attr('ws_ame_' . $slug . '_tab'),
2421
  $slug === $this->current_tab ? ' nav-tab-active' : '',
2422
  $title
2443
  require dirname(__FILE__) . '/settings-page.php';
2444
  }
2445
 
2446
+ /**
2447
+ * Get the fully qualified URL of the plugin page, i.e. "Settings -> Menu Editor [Pro]".
2448
+ *
2449
+ * @param array $extra_query_args List of query arguments to append to the URL. Format: [param => value].
2450
+ * @return string
2451
+ */
2452
+ public function get_plugin_page_url($extra_query_args = array()) {
2453
+ $url = self_admin_url($this->settings_link);
2454
+ if ( !empty($extra_query_args) ) {
2455
+ $url = add_query_arg($extra_query_args, $url);
2456
+ }
2457
+ return $url;
2458
+ }
2459
+
2460
  /**
2461
  * Get the fully qualified URL of the "Settings" sub-section of our plugin page.
2462
  *
2463
  * @return string
2464
  */
2465
  private function get_settings_page_url() {
2466
+ return $this->get_plugin_page_url(array('sub_section' => 'settings'));
2467
  }
2468
 
2469
  /**
2471
  *
2472
  * @return bool
2473
  */
2474
+ public function is_editor_page() {
2475
+ return $this->is_tab_open('editor') || $this->is_tab_open('network-admin-menu');
2476
  }
2477
 
2478
  /**
2495
  && ($this->current_tab === $tab_slug)
2496
  && isset($this->get['page']) && ($this->get['page'] == 'menu_editor');
2497
  }
2498
+
2499
  /**
2500
+ * Get the list of virtual capabilities.
 
 
 
 
2501
  *
 
2502
  * @uses self::$cached_virtual_caps to cache the generated list of caps.
2503
  *
2504
+ * @param int|null $mode
2505
  * @return array A list of capability => [role1 => true, ... roleN => true] assignments.
2506
  */
2507
+ function get_virtual_caps($mode = null) {
2508
+ if ( $mode === null ) {
2509
+ $mode = self::ALL_VIRTUAL_CAPS;
2510
+ }
2511
+
2512
  if ( $this->cached_virtual_caps !== null ) {
2513
+ return $this->cached_virtual_caps[$mode];
2514
  }
2515
 
 
2516
  $custom_menu = $this->load_custom_menu();
2517
  if ( $custom_menu === null ){
2518
+ return array();
2519
  }
2520
 
2521
+ if ( isset($custom_menu['prebuilt_virtual_caps']) ) {
2522
+ $this->cached_virtual_caps = $custom_menu['prebuilt_virtual_caps'];
2523
+ } else {
2524
+ $this->cached_virtual_caps = $this->build_virtual_capability_list($custom_menu);
2525
+ }
2526
+
2527
+ return $this->cached_virtual_caps[$mode];
2528
+ }
2529
+
2530
+ /**
2531
+ * Generate a list of "virtual" capabilities that should be granted to specific actors.
2532
+ *
2533
+ * This is based on grant_access settings for the custom menu and enables selected
2534
+ * roles and users to access menu items that they ordinarily would not be able to.
2535
+ *
2536
+ * @uses self::get_virtual_caps_for() to actually generate the caps.
2537
+ *
2538
+ * @param array $custom_menu
2539
+ * @return array
2540
+ */
2541
+ private function build_virtual_capability_list($custom_menu) {
2542
  //Include directly granted capabilities.
2543
+ $grantedCaps = array();
2544
  if ( !empty($custom_menu['granted_capabilities']) ) {
2545
  foreach ($custom_menu['granted_capabilities'] as $actor => $capabilities) {
2546
  foreach ($capabilities as $capability => $allow) {
2547
+ $grantedCaps[$actor][$capability] = (bool)(is_array($allow) ? $allow[0] : $allow);
2548
  }
2549
  }
2550
  }
2551
 
2552
+ //Include caps that are required to access menu items (grant_access).
2553
+ $menuCaps = array();
2554
  foreach($custom_menu['tree'] as $item) {
2555
+ $menuCaps = self::array_replace_recursive($menuCaps, $this->get_virtual_caps_for($item));
2556
  }
2557
 
2558
+ //grant_access settings on individual items have precedence.
2559
+ $allCaps = self::array_replace_recursive($grantedCaps, $menuCaps);
2560
+
2561
+ return array(
2562
+ self::DIRECTLY_GRANTED_VIRTUAL_CAPS => $grantedCaps,
2563
+ self::ALL_VIRTUAL_CAPS => $allCaps,
2564
+ );
2565
  }
2566
 
2567
  private function get_virtual_caps_for($item) {
2611
  } elseif ( $capability === 'delete_site' ) {
2612
  return 'manage_options';
2613
  }
2614
+
2615
+ static $category_caps = array(
2616
+ 'manage_post_tags' => true,
2617
+ 'edit_categories' => true,
2618
+ 'edit_post_tags' => true,
2619
+ 'delete_categories' => true,
2620
+ 'delete_post_tags' => true,
2621
+ );
2622
+ if ( isset($category_caps[$capability]) ) {
2623
+ return 'manage_categories';
2624
+ }
2625
+
2626
+ if (($capability === 'assign_categories') || ($capability === 'assign_post_tags')) {
2627
+ return 'edit_posts';
2628
+ }
2629
+
2630
  return $capability;
2631
  }
2632
 
2643
 
2644
  if ($this->options['menu_config_scope'] === 'site') {
2645
  $this->cached_custom_menu = null;
2646
+ $this->loaded_menu_config_id = null;
2647
  }
2648
  }
2649
 
2747
  'sort_column' => 'post_modified',
2748
  'sort_order' => 'DESC',
2749
  'hierarchical' => false,
2750
+ 'post_status' => array('publish', 'private'),
2751
  'number' => 50, //Semi-arbitrary. We do need a limit - some users could have thousands of pages.
2752
  ));
2753
  /** @var WP_Post[] $pages */
includes/menu-item.php CHANGED
@@ -34,7 +34,7 @@ abstract class ameMenuItem {
34
  * Convert a WP menu structure to an associative array.
35
  *
36
  * @param array $item An menu item.
37
- * @param int $position The position (index) of the the menu item.
38
  * @param string|null $parent The slug of the parent menu that owns this item. Null for top level menus.
39
  * @return array
40
  */
@@ -70,10 +70,11 @@ abstract class ameMenuItem {
70
  }
71
 
72
  //Flag plugin pages
73
- $item['is_plugin_page'] = (get_plugin_page_hook($item['file'], strval($parent)) != null);
 
74
 
75
  if ( !$item['separator'] ) {
76
- $item['url'] = self::generate_url($item['file'], strval($parent));
77
  }
78
 
79
  $item['template_id'] = self::template_id($item, $parent);
@@ -212,7 +213,7 @@ abstract class ameMenuItem {
212
  return strval($parent_file) . '>' . $item;
213
  }
214
 
215
- if ( self::get($item, 'custom') ) {
216
  return '';
217
  }
218
 
@@ -475,7 +476,7 @@ abstract class ameMenuItem {
475
  * @return int
476
  */
477
  public static function compare_position($a, $b){
478
- $result = self::get($a, 'position', 0) - self::get($b, 'position', 0);
479
  //Support for non-integer positions.
480
  if ($result > 0) {
481
  return 1;
@@ -490,9 +491,10 @@ abstract class ameMenuItem {
490
  *
491
  * @param string $item_slug
492
  * @param string $parent_slug
 
493
  * @return string An URL relative to the /wp-admin/ directory.
494
  */
495
- public static function generate_url($item_slug, $parent_slug = '') {
496
  $menu_url = is_array($item_slug) ? self::get($item_slug, 'file') : $item_slug;
497
  $parent_url = !empty($parent_slug) ? $parent_slug : 'admin.php';
498
 
@@ -505,32 +507,37 @@ abstract class ameMenuItem {
505
  return $menu_url;
506
  }
507
 
508
- if ( self::is_hook_or_plugin_page($menu_url, $parent_url) ) {
509
  $parent_file = self::remove_query_from($parent_url);
510
  $base_file = self::is_wp_admin_file($parent_file) ? $parent_url : 'admin.php';
511
- $url = add_query_arg(array('page' => $menu_url), $base_file);
 
 
 
512
  } else {
513
  $url = $menu_url;
514
  }
515
  return $url;
516
  }
517
 
518
- private static function is_hook_or_plugin_page($page_url, $parent_page_url = '') {
519
  if ( empty($parent_page_url) ) {
520
  $parent_page_url = 'admin.php';
521
  }
522
  $pageFile = self::remove_query_from($page_url);
523
 
524
- //Files in /wp-admin are part of WP core so they're not plugin pages.
525
- if ( self::is_wp_admin_file($pageFile) ) {
526
- return false;
527
  }
528
-
529
- $hasHook = (get_plugin_page_hook($page_url, $parent_page_url) !== null);
530
  if ( $hasHook ) {
531
  return true;
532
  }
533
 
 
 
 
 
 
534
  /*
535
  * Special case: Absolute paths.
536
  *
34
  * Convert a WP menu structure to an associative array.
35
  *
36
  * @param array $item An menu item.
37
+ * @param int|string $position The position (index) of the the menu item.
38
  * @param string|null $parent The slug of the parent menu that owns this item. Null for top level menus.
39
  * @return array
40
  */
70
  }
71
 
72
  //Flag plugin pages
73
+ $has_hook = (get_plugin_page_hook($item['file'], strval($parent)) != null);
74
+ $item['is_plugin_page'] = $has_hook;
75
 
76
  if ( !$item['separator'] ) {
77
+ $item['url'] = self::generate_url($item['file'], strval($parent), $has_hook);
78
  }
79
 
80
  $item['template_id'] = self::template_id($item, $parent);
213
  return strval($parent_file) . '>' . $item;
214
  }
215
 
216
+ if ( !empty($item['custom']) ) {
217
  return '';
218
  }
219
 
476
  * @return int
477
  */
478
  public static function compare_position($a, $b){
479
+ $result = floatval(self::get($a, 'position', 0)) - floatval(self::get($b, 'position', 0));
480
  //Support for non-integer positions.
481
  if ($result > 0) {
482
  return 1;
491
  *
492
  * @param string $item_slug
493
  * @param string $parent_slug
494
+ * @param bool|null $has_hook
495
  * @return string An URL relative to the /wp-admin/ directory.
496
  */
497
+ public static function generate_url($item_slug, $parent_slug = '', $has_hook = null) {
498
  $menu_url = is_array($item_slug) ? self::get($item_slug, 'file') : $item_slug;
499
  $parent_url = !empty($parent_slug) ? $parent_slug : 'admin.php';
500
 
507
  return $menu_url;
508
  }
509
 
510
+ if ( self::is_hook_or_plugin_page($menu_url, $parent_url, $has_hook) ) {
511
  $parent_file = self::remove_query_from($parent_url);
512
  $base_file = self::is_wp_admin_file($parent_file) ? $parent_url : 'admin.php';
513
+ //add_query_arg() might be more robust, but it's significantly slower.
514
+ $url = $base_file
515
+ . ((strpos($base_file, '?') === false) ? '?' : '&')
516
+ . 'page=' . urlencode($menu_url);
517
  } else {
518
  $url = $menu_url;
519
  }
520
  return $url;
521
  }
522
 
523
+ private static function is_hook_or_plugin_page($page_url, $parent_page_url = '', $hasHook = null) {
524
  if ( empty($parent_page_url) ) {
525
  $parent_page_url = 'admin.php';
526
  }
527
  $pageFile = self::remove_query_from($page_url);
528
 
529
+ if ( $hasHook === null ) {
530
+ $hasHook = (get_plugin_page_hook($page_url, $parent_page_url) !== null);
 
531
  }
 
 
532
  if ( $hasHook ) {
533
  return true;
534
  }
535
 
536
+ //Files in /wp-admin are part of WP core so they're not plugin pages.
537
+ if ( self::is_wp_admin_file($pageFile) ) {
538
+ return false;
539
+ }
540
+
541
  /*
542
  * Special case: Absolute paths.
543
  *
includes/menu.php CHANGED
@@ -147,6 +147,11 @@ abstract class ameMenu {
147
  $menu['has_modified_dashicons'] = (bool)$arr['has_modified_dashicons'];
148
  }
149
 
 
 
 
 
 
150
  return $menu;
151
  }
152
 
147
  $menu['has_modified_dashicons'] = (bool)$arr['has_modified_dashicons'];
148
  }
149
 
150
+ //Copy the pre-generated list of virtual capabilities.
151
+ if ( isset($arr['prebuilt_virtual_caps']) ) {
152
+ $menu['prebuilt_virtual_caps'] = $arr['prebuilt_virtual_caps'];
153
+ }
154
+
155
  return $menu;
156
  }
157
 
includes/module.php CHANGED
@@ -21,6 +21,10 @@ abstract class ameModule {
21
  $this->moduleId = basename($this->moduleDir);
22
  }
23
 
 
 
 
 
24
  add_action('admin_menu_editor-register_scripts', array($this, 'registerScripts'));
25
 
26
  //Register the module tab.
@@ -33,6 +37,16 @@ abstract class ameModule {
33
  }
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
36
  public function addTab($tabs) {
37
  $tabs[$this->tabSlug] = !empty($this->tabTitle) ? $this->tabTitle : $this->tabSlug;
38
  return $tabs;
@@ -50,13 +64,10 @@ abstract class ameModule {
50
 
51
  protected function getTabUrl($queryParameters = array()) {
52
  $queryParameters = array_merge(
53
- array(
54
- 'page' => 'menu_editor',
55
- 'sub_section' => $this->tabSlug
56
- ),
57
  $queryParameters
58
  );
59
- return add_query_arg($queryParameters, admin_url('options-general.php'));
60
  }
61
 
62
  protected function outputMainTemplate() {
21
  $this->moduleId = basename($this->moduleDir);
22
  }
23
 
24
+ if ( !$this->isEnabledForRequest() ) {
25
+ return;
26
+ }
27
+
28
  add_action('admin_menu_editor-register_scripts', array($this, 'registerScripts'));
29
 
30
  //Register the module tab.
37
  }
38
  }
39
 
40
+ /**
41
+ * Does this module need to do anything for the current request?
42
+ *
43
+ * For example, some modules work in the normal dashboard but not in the network admin.
44
+ * Other modules don't need to run during AJAX requests or when WP is running Cron jobs.
45
+ */
46
+ protected function isEnabledForRequest() {
47
+ return true;
48
+ }
49
+
50
  public function addTab($tabs) {
51
  $tabs[$this->tabSlug] = !empty($this->tabTitle) ? $this->tabTitle : $this->tabSlug;
52
  return $tabs;
64
 
65
  protected function getTabUrl($queryParameters = array()) {
66
  $queryParameters = array_merge(
67
+ array('sub_section' => $this->tabSlug),
 
 
 
68
  $queryParameters
69
  );
70
+ return $this->menuEditor->get_plugin_page_url($queryParameters);
71
  }
72
 
73
  protected function outputMainTemplate() {
includes/shadow_plugin_framework.php CHANGED
@@ -241,12 +241,16 @@ class MenuEd_ShadowPluginFramework {
241
  $hook = substr($method->name, 5);
242
  //Add the hook. Uses add_filter because add_action is simply a wrapper of the same.
243
  add_filter($hook, array(&$this, $method->name),
244
- $this->magic_hook_priority, $method->getNumberOfParameters());
245
  }
246
  }
247
 
248
  unset($class);
249
  }
 
 
 
 
250
 
251
 
252
  /**
241
  $hook = substr($method->name, 5);
242
  //Add the hook. Uses add_filter because add_action is simply a wrapper of the same.
243
  add_filter($hook, array(&$this, $method->name),
244
+ $this->get_magic_hook_priority(), $method->getNumberOfParameters());
245
  }
246
  }
247
 
248
  unset($class);
249
  }
250
+
251
+ public function get_magic_hook_priority() {
252
+ return $this->magic_hook_priority;
253
+ }
254
 
255
 
256
  /**
js/actor-manager.js CHANGED
@@ -116,9 +116,9 @@ var AmeSuperAdmin = (function (_super) {
116
  //The Super Admin has all possible capabilities except the special "do_not_allow" flag.
117
  return (capability !== 'do_not_allow');
118
  };
 
119
  return AmeSuperAdmin;
120
  }(AmeBaseActor));
121
- AmeSuperAdmin.permanentActorId = 'special:super_admin';
122
  var AmeActorManager = (function () {
123
  function AmeActorManager(roles, users, isMultisite) {
124
  if (isMultisite === void 0) { isMultisite = false; }
@@ -127,6 +127,8 @@ var AmeActorManager = (function () {
127
  this.users = {};
128
  this.grantedCapabilities = {};
129
  this.isMultisite = false;
 
 
130
  this.suggestedCapabilities = [];
131
  this.isMultisite = !!isMultisite;
132
  AmeActorManager._.forEach(roles, function (roleDetails, id) {
@@ -140,6 +142,21 @@ var AmeActorManager = (function () {
140
  if (this.isMultisite) {
141
  this.superAdmin = new AmeSuperAdmin();
142
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  }
144
  AmeActorManager.prototype.actorCanAccess = function (actorId, grantAccess, defaultCapability) {
145
  if (defaultCapability === void 0) { defaultCapability = null; }
@@ -194,7 +211,7 @@ var AmeActorManager = (function () {
194
  if (capability === 'exist') {
195
  return true;
196
  }
197
- capability = AmeActorManager.mapMetaCap(capability);
198
  var result = null;
199
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
200
  //Step #2: Check granted capabilities. Default on, but can be skipped.
@@ -238,13 +255,23 @@ var AmeActorManager = (function () {
238
  }
239
  return false;
240
  };
241
- AmeActorManager.mapMetaCap = function (capability) {
242
  if (capability === 'customize') {
243
  return 'edit_theme_options';
244
  }
245
  else if (capability === 'delete_site') {
246
  return 'manage_options';
247
  }
 
 
 
 
 
 
 
 
 
 
248
  return capability;
249
  };
250
  /* -------------------------------
@@ -291,18 +318,18 @@ var AmeActorManager = (function () {
291
  * Grant or deny a capability to an actor.
292
  */
293
  AmeActorManager.prototype.setCap = function (actor, capability, hasCap, sourceType, sourceName) {
294
- AmeActorManager.setCapInContext(this.grantedCapabilities, actor, capability, hasCap, sourceType, sourceName);
295
  };
296
- AmeActorManager.setCapInContext = function (context, actor, capability, hasCap, sourceType, sourceName) {
297
- capability = AmeActorManager.mapMetaCap(capability);
298
  var grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
299
  AmeActorManager._.set(context, [actor, capability], grant);
300
  };
301
  AmeActorManager.prototype.resetCap = function (actor, capability) {
302
- AmeActorManager.resetCapInContext(this.grantedCapabilities, actor, capability);
303
  };
304
- AmeActorManager.resetCapInContext = function (context, actor, capability) {
305
- capability = AmeActorManager.mapMetaCap(capability);
306
  if (AmeActorManager._.has(context, [actor, capability])) {
307
  delete context[actor][capability];
308
  }
@@ -440,9 +467,9 @@ var AmeActorManager = (function () {
440
  AmeActorManager.prototype.getSuggestedCapabilities = function () {
441
  return this.suggestedCapabilities;
442
  };
 
443
  return AmeActorManager;
444
  }());
445
- AmeActorManager._ = wsAmeLodash;
446
  if (typeof wsAmeActorData !== 'undefined') {
447
  AmeActors = new AmeActorManager(wsAmeActorData.roles, wsAmeActorData.users, wsAmeActorData.isMultisite);
448
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
116
  //The Super Admin has all possible capabilities except the special "do_not_allow" flag.
117
  return (capability !== 'do_not_allow');
118
  };
119
+ AmeSuperAdmin.permanentActorId = 'special:super_admin';
120
  return AmeSuperAdmin;
121
  }(AmeBaseActor));
 
122
  var AmeActorManager = (function () {
123
  function AmeActorManager(roles, users, isMultisite) {
124
  if (isMultisite === void 0) { isMultisite = false; }
127
  this.users = {};
128
  this.grantedCapabilities = {};
129
  this.isMultisite = false;
130
+ this.exclusiveSuperAdminCapabilities = {};
131
+ this.tagMetaCaps = {};
132
  this.suggestedCapabilities = [];
133
  this.isMultisite = !!isMultisite;
134
  AmeActorManager._.forEach(roles, function (roleDetails, id) {
142
  if (this.isMultisite) {
143
  this.superAdmin = new AmeSuperAdmin();
144
  }
145
+ var exclusiveCaps = [
146
+ 'update_core', 'update_plugins', 'delete_plugins', 'install_plugins', 'upload_plugins', 'update_themes',
147
+ 'delete_themes', 'install_themes', 'upload_themes', 'update_core', 'edit_css', 'unfiltered_html',
148
+ 'edit_files', 'edit_plugins', 'edit_themes', 'delete_user', 'delete_users'
149
+ ];
150
+ for (var i = 0; i < exclusiveCaps.length; i++) {
151
+ this.exclusiveSuperAdminCapabilities[exclusiveCaps[i]] = true;
152
+ }
153
+ var tagMetaCaps = [
154
+ 'manage_post_tags', 'edit_categories', 'edit_post_tags', 'delete_categories',
155
+ 'delete_post_tags'
156
+ ];
157
+ for (var i = 0; i < tagMetaCaps.length; i++) {
158
+ this.tagMetaCaps[tagMetaCaps[i]] = true;
159
+ }
160
  }
161
  AmeActorManager.prototype.actorCanAccess = function (actorId, grantAccess, defaultCapability) {
162
  if (defaultCapability === void 0) { defaultCapability = null; }
211
  if (capability === 'exist') {
212
  return true;
213
  }
214
+ capability = this.mapMetaCap(capability);
215
  var result = null;
216
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
217
  //Step #2: Check granted capabilities. Default on, but can be skipped.
255
  }
256
  return false;
257
  };
258
+ AmeActorManager.prototype.mapMetaCap = function (capability) {
259
  if (capability === 'customize') {
260
  return 'edit_theme_options';
261
  }
262
  else if (capability === 'delete_site') {
263
  return 'manage_options';
264
  }
265
+ //In Multisite, some capabilities are only available to Super Admins.
266
+ if (this.isMultisite && this.exclusiveSuperAdminCapabilities.hasOwnProperty(capability)) {
267
+ return AmeSuperAdmin.permanentActorId;
268
+ }
269
+ if (this.tagMetaCaps.hasOwnProperty(capability)) {
270
+ return 'manage_categories';
271
+ }
272
+ if ((capability === 'assign_categories') || (capability === 'assign_post_tags')) {
273
+ return 'edit_posts';
274
+ }
275
  return capability;
276
  };
277
  /* -------------------------------
318
  * Grant or deny a capability to an actor.
319
  */
320
  AmeActorManager.prototype.setCap = function (actor, capability, hasCap, sourceType, sourceName) {
321
+ this.setCapInContext(this.grantedCapabilities, actor, capability, hasCap, sourceType, sourceName);
322
  };
323
+ AmeActorManager.prototype.setCapInContext = function (context, actor, capability, hasCap, sourceType, sourceName) {
324
+ capability = this.mapMetaCap(capability);
325
  var grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
326
  AmeActorManager._.set(context, [actor, capability], grant);
327
  };
328
  AmeActorManager.prototype.resetCap = function (actor, capability) {
329
+ this.resetCapInContext(this.grantedCapabilities, actor, capability);
330
  };
331
+ AmeActorManager.prototype.resetCapInContext = function (context, actor, capability) {
332
+ capability = this.mapMetaCap(capability);
333
  if (AmeActorManager._.has(context, [actor, capability])) {
334
  delete context[actor][capability];
335
  }
467
  AmeActorManager.prototype.getSuggestedCapabilities = function () {
468
  return this.suggestedCapabilities;
469
  };
470
+ AmeActorManager._ = wsAmeLodash;
471
  return AmeActorManager;
472
  }());
 
473
  if (typeof wsAmeActorData !== 'undefined') {
474
  AmeActors = new AmeActorManager(wsAmeActorData.roles, wsAmeActorData.users, wsAmeActorData.isMultisite);
475
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
js/actor-manager.ts CHANGED
@@ -177,8 +177,11 @@ class AmeActorManager {
177
  private users: {[userLogin: string] : AmeUser} = {};
178
  private grantedCapabilities: AmeGrantedCapabilityMap = {};
179
 
180
- private isMultisite: boolean = false;
181
  private superAdmin: AmeSuperAdmin;
 
 
 
182
 
183
  private suggestedCapabilities: AmeCapabilitySuggestion[] = [];
184
 
@@ -198,6 +201,23 @@ class AmeActorManager {
198
  if (this.isMultisite) {
199
  this.superAdmin = new AmeSuperAdmin();
200
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
 
203
  actorCanAccess(
@@ -265,7 +285,7 @@ class AmeActorManager {
265
  return true;
266
  }
267
 
268
- capability = AmeActorManager.mapMetaCap(capability);
269
  let result = null;
270
 
271
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
@@ -315,12 +335,22 @@ class AmeActorManager {
315
  return false;
316
  }
317
 
318
- private static mapMetaCap(capability: string): string {
319
  if (capability === 'customize') {
320
  return 'edit_theme_options';
321
  } else if (capability === 'delete_site') {
322
  return 'manage_options';
323
  }
 
 
 
 
 
 
 
 
 
 
324
  return capability;
325
  }
326
 
@@ -378,10 +408,10 @@ class AmeActorManager {
378
  * Grant or deny a capability to an actor.
379
  */
380
  setCap(actor: string, capability: string, hasCap: boolean, sourceType?, sourceName?) {
381
- AmeActorManager.setCapInContext(this.grantedCapabilities, actor, capability, hasCap, sourceType, sourceName);
382
  }
383
 
384
- static setCapInContext(
385
  context: AmeGrantedCapabilityMap,
386
  actor: string,
387
  capability: string,
@@ -389,18 +419,18 @@ class AmeActorManager {
389
  sourceType?: string,
390
  sourceName?: string
391
  ) {
392
- capability = AmeActorManager.mapMetaCap(capability);
393
 
394
  const grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
395
  AmeActorManager._.set(context, [actor, capability], grant);
396
  }
397
 
398
  resetCap(actor: string, capability: string) {
399
- AmeActorManager.resetCapInContext(this.grantedCapabilities, actor, capability);
400
  }
401
 
402
- static resetCapInContext(context: AmeGrantedCapabilityMap, actor: string, capability: string) {
403
- capability = AmeActorManager.mapMetaCap(capability);
404
 
405
  if (AmeActorManager._.has(context, [actor, capability])) {
406
  delete context[actor][capability];
177
  private users: {[userLogin: string] : AmeUser} = {};
178
  private grantedCapabilities: AmeGrantedCapabilityMap = {};
179
 
180
+ public readonly isMultisite: boolean = false;
181
  private superAdmin: AmeSuperAdmin;
182
+ private exclusiveSuperAdminCapabilities = {};
183
+
184
+ private tagMetaCaps = {};
185
 
186
  private suggestedCapabilities: AmeCapabilitySuggestion[] = [];
187
 
201
  if (this.isMultisite) {
202
  this.superAdmin = new AmeSuperAdmin();
203
  }
204
+
205
+ const exclusiveCaps: string[] = [
206
+ 'update_core', 'update_plugins', 'delete_plugins', 'install_plugins', 'upload_plugins', 'update_themes',
207
+ 'delete_themes', 'install_themes', 'upload_themes', 'update_core', 'edit_css', 'unfiltered_html',
208
+ 'edit_files', 'edit_plugins', 'edit_themes', 'delete_user', 'delete_users'
209
+ ];
210
+ for (let i = 0; i < exclusiveCaps.length; i++) {
211
+ this.exclusiveSuperAdminCapabilities[exclusiveCaps[i]] = true;
212
+ }
213
+
214
+ const tagMetaCaps = [
215
+ 'manage_post_tags', 'edit_categories', 'edit_post_tags', 'delete_categories',
216
+ 'delete_post_tags'
217
+ ];
218
+ for (let i = 0; i < tagMetaCaps.length; i++) {
219
+ this.tagMetaCaps[tagMetaCaps[i]] = true;
220
+ }
221
  }
222
 
223
  actorCanAccess(
285
  return true;
286
  }
287
 
288
+ capability = this.mapMetaCap(capability);
289
  let result = null;
290
 
291
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
335
  return false;
336
  }
337
 
338
+ private mapMetaCap(capability: string): string {
339
  if (capability === 'customize') {
340
  return 'edit_theme_options';
341
  } else if (capability === 'delete_site') {
342
  return 'manage_options';
343
  }
344
+ //In Multisite, some capabilities are only available to Super Admins.
345
+ if (this.isMultisite && this.exclusiveSuperAdminCapabilities.hasOwnProperty(capability)) {
346
+ return AmeSuperAdmin.permanentActorId;
347
+ }
348
+ if (this.tagMetaCaps.hasOwnProperty(capability)) {
349
+ return 'manage_categories';
350
+ }
351
+ if ((capability === 'assign_categories') || (capability === 'assign_post_tags')) {
352
+ return 'edit_posts';
353
+ }
354
  return capability;
355
  }
356
 
408
  * Grant or deny a capability to an actor.
409
  */
410
  setCap(actor: string, capability: string, hasCap: boolean, sourceType?, sourceName?) {
411
+ this.setCapInContext(this.grantedCapabilities, actor, capability, hasCap, sourceType, sourceName);
412
  }
413
 
414
+ public setCapInContext(
415
  context: AmeGrantedCapabilityMap,
416
  actor: string,
417
  capability: string,
419
  sourceType?: string,
420
  sourceName?: string
421
  ) {
422
+ capability = this.mapMetaCap(capability);
423
 
424
  const grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
425
  AmeActorManager._.set(context, [actor, capability], grant);
426
  }
427
 
428
  resetCap(actor: string, capability: string) {
429
+ this.resetCapInContext(this.grantedCapabilities, actor, capability);
430
  }
431
 
432
+ public resetCapInContext(context: AmeGrantedCapabilityMap, actor: string, capability: string) {
433
+ capability = this.mapMetaCap(capability);
434
 
435
  if (AmeActorManager._.has(context, [actor, capability])) {
436
  delete context[actor][capability];
js/menu-editor.js CHANGED
@@ -1226,12 +1226,15 @@ function updateActorAccessUi(containerNode) {
1226
  var currentUserActor = 'user:' + wsEditorData.currentUserLogin;
1227
  var otherActors = _(wsEditorData.actors).keys().without(currentUserActor, 'special:super_admin').value(),
1228
  hiddenFromCurrentUser = ! actorCanAccessMenu(menuItem, currentUserActor),
1229
- hiddenFromOthers = ! _.some(otherActors, _.curry(actorCanAccessMenu, 2)(menuItem));
 
1230
  setMenuFlag(
1231
  containerNode,
1232
  'hidden_from_others',
1233
  hiddenFromOthers,
1234
- hiddenFromCurrentUser ? 'Hidden from everyone' : 'Hidden from everyone except you'
 
 
1235
  );
1236
  }
1237
 
@@ -4834,5 +4837,8 @@ jQuery(function($){
4834
  });
4835
 
4836
  //Move our options into the screen meta panel
4837
- $('#adv-settings').empty().append(screenOptions.show());
 
 
 
4838
  });
1226
  var currentUserActor = 'user:' + wsEditorData.currentUserLogin;
1227
  var otherActors = _(wsEditorData.actors).keys().without(currentUserActor, 'special:super_admin').value(),
1228
  hiddenFromCurrentUser = ! actorCanAccessMenu(menuItem, currentUserActor),
1229
+ hiddenFromOthers = ! _.some(otherActors, _.curry(actorCanAccessMenu, 2)(menuItem)),
1230
+ visibleForSuperAdmin = AmeActors.isMultisite && actorCanAccessMenu(menuItem, 'special:super_admin');
1231
  setMenuFlag(
1232
  containerNode,
1233
  'hidden_from_others',
1234
  hiddenFromOthers,
1235
+ hiddenFromCurrentUser
1236
+ ? 'Hidden from everyone'
1237
+ : ('Hidden from everyone except you' + (visibleForSuperAdmin ? ' and Super Admins' : ''))
1238
  );
1239
  }
1240
 
4837
  });
4838
 
4839
  //Move our options into the screen meta panel
4840
+ var advSettings = $('#adv-settings');
4841
+ if (advSettings.length > 0) {
4842
+ advSettings.empty().append(screenOptions.show());
4843
+ }
4844
  });
menu-editor.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
- Version: 1.8
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
+ Version: 1.8.1
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
modules/access-editor/access-editor.js CHANGED
@@ -242,10 +242,10 @@ window.AmeItemAccessEditor = (function ($) {
242
  hasCapWhenReset;
243
 
244
  //Don't create custom settings unless necessary.
245
- AmeActorManager.resetCapInContext(unsavedCapabilities, selectedActor, capability);
246
  hasCapWhenReset = AmeCapabilityManager.hasCap(selectedActor, capability, unsavedCapabilities);
247
  if (isAllowed !== hasCapWhenReset) {
248
- AmeActorManager.setCapInContext(
249
  unsavedCapabilities,
250
  selectedActor,
251
  capability,
@@ -277,7 +277,7 @@ window.AmeItemAccessEditor = (function ($) {
277
  hasCapByDefault = AmeCapabilityManager.hasCapByDefault(selectedActor, itemRequiredCap);
278
 
279
  if (isAllowed && hasCapByDefault && !hasCap) {
280
- AmeActorManager.setCapInContext(
281
  unsavedCapabilities,
282
  selectedActor,
283
  itemRequiredCap,
242
  hasCapWhenReset;
243
 
244
  //Don't create custom settings unless necessary.
245
+ AmeCapabilityManager.resetCapInContext(unsavedCapabilities, selectedActor, capability);
246
  hasCapWhenReset = AmeCapabilityManager.hasCap(selectedActor, capability, unsavedCapabilities);
247
  if (isAllowed !== hasCapWhenReset) {
248
+ AmeCapabilityManager.setCapInContext(
249
  unsavedCapabilities,
250
  selectedActor,
251
  capability,
277
  hasCapByDefault = AmeCapabilityManager.hasCapByDefault(selectedActor, itemRequiredCap);
278
 
279
  if (isAllowed && hasCapByDefault && !hasCap) {
280
+ AmeCapabilityManager.setCapInContext(
281
  unsavedCapabilities,
282
  selectedActor,
283
  itemRequiredCap,
modules/actor-selector/actor-selector.js CHANGED
@@ -196,7 +196,6 @@ var AmeActorSelector = (function () {
196
  }
197
  return name;
198
  };
 
199
  return AmeActorSelector;
200
  }());
201
- AmeActorSelector._ = wsAmeLodash;
202
- //# sourceMappingURL=actor-selector.js.map
196
  }
197
  return name;
198
  };
199
+ AmeActorSelector._ = wsAmeLodash;
200
  return AmeActorSelector;
201
  }());
 
 
modules/plugin-visibility/plugin-visibility-template.php CHANGED
@@ -1,15 +1,14 @@
1
- <?php do_action('admin_menu_editor-display_header'); ?>
 
 
 
 
 
 
2
 
3
  <div id="ame-plugin-visibility-editor">
4
  <form method="post" data-bind="submit: saveChanges" class="ame-pv-save-form" action="<?php
5
- echo esc_attr(add_query_arg(
6
- array(
7
- 'page' => 'menu_editor',
8
- 'noheader' => '1',
9
- 'sub_section' => amePluginVisibility::TAB_SLUG,
10
- ),
11
- admin_url('options-general.php')
12
- ));
13
  ?>">
14
 
15
  <?php submit_button('Save Changes', 'primary', 'submit', false); ?>
1
+ <?php
2
+ /**
3
+ * @var string $tabUrl Fully qualified URL of the "Plugins" tab.
4
+ */
5
+
6
+ do_action('admin_menu_editor-display_header');
7
+ ?>
8
 
9
  <div id="ame-plugin-visibility-editor">
10
  <form method="post" data-bind="submit: saveChanges" class="ame-pv-save-form" action="<?php
11
+ echo esc_attr(add_query_arg(array('noheader' => '1'), $tabUrl));
 
 
 
 
 
 
 
12
  ?>">
13
 
14
  <?php submit_button('Save Changes', 'primary', 'submit', false); ?>
modules/plugin-visibility/plugin-visibility.php CHANGED
@@ -192,6 +192,11 @@ class amePluginVisibility {
192
  $user = wp_get_current_user();
193
  $settings = $this->getSettings();
194
 
 
 
 
 
 
195
  $pluginFileNames = array_keys($plugins);
196
  foreach($pluginFileNames as $fileName) {
197
  //Remove all hidden plugins.
@@ -288,6 +293,9 @@ class amePluginVisibility {
288
  }
289
 
290
  public function displayUi() {
 
 
 
291
  require dirname(__FILE__) . '/plugin-visibility-template.php';
292
  }
293
 
@@ -299,7 +307,7 @@ class amePluginVisibility {
299
  $this->settings = json_decode($post['settings'], true);
300
  $this->saveSettings();
301
 
302
- $params = array('updated' => 1);
303
 
304
  //Re-select the same actor.
305
  if ( !empty($post['selected_actor']) ) {
@@ -313,13 +321,10 @@ class amePluginVisibility {
313
 
314
  private function getTabUrl($queryParameters = array()) {
315
  $queryParameters = array_merge(
316
- array(
317
- 'page' => 'menu_editor',
318
- 'sub_section' => self::TAB_SLUG
319
- ),
320
  $queryParameters
321
  );
322
- return add_query_arg($queryParameters, admin_url('options-general.php'));
323
  }
324
 
325
  public function enqueueScripts() {
192
  $user = wp_get_current_user();
193
  $settings = $this->getSettings();
194
 
195
+ //Don't try to hide plugins outside the WP admin. It prevents WP-CLI from seeing all installed plugins.
196
+ if ( !$user->exists() || !is_admin() ) {
197
+ return $plugins;
198
+ }
199
+
200
  $pluginFileNames = array_keys($plugins);
201
  foreach($pluginFileNames as $fileName) {
202
  //Remove all hidden plugins.
293
  }
294
 
295
  public function displayUi() {
296
+ /** @noinspection PhpUnusedLocalVariableInspection Used in the "action" attribute of the settings form. */
297
+ $tabUrl = $this->getTabUrl();
298
+
299
  require dirname(__FILE__) . '/plugin-visibility-template.php';
300
  }
301
 
307
  $this->settings = json_decode($post['settings'], true);
308
  $this->saveSettings();
309
 
310
+ $params = array('message' => 1);
311
 
312
  //Re-select the same actor.
313
  if ( !empty($post['selected_actor']) ) {
321
 
322
  private function getTabUrl($queryParameters = array()) {
323
  $queryParameters = array_merge(
324
+ array('sub_section' => self::TAB_SLUG),
 
 
 
325
  $queryParameters
326
  );
327
+ return $this->menuEditor->get_plugin_page_url($queryParameters);
328
  }
329
 
330
  public function enqueueScripts() {
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: whiteshadow
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
  Requires at least: 4.1
6
- Tested up to: 4.8
7
- Stable tag: 1.8
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
@@ -63,6 +63,12 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
66
  = 1.8 =
67
  * You can edit plugin names and descriptions through the "Plugins" tab. This only changes how plugins are displayed on the "Plugins" page. It doesn't affect plugin files on disk.
68
  * Added an option to highlight new menu items. This feature is off by default. You can enable it in the "Settings" tab.
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
  Requires at least: 4.1
6
+ Tested up to: 4.9
7
+ Stable tag: 1.8.1
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
63
 
64
  == Changelog ==
65
 
66
+ = 1.8.1 =
67
+ * Added a workaround for a buggy "defer_parsing_of_js" code snippet that some users have added to their functions.php. This snippet produces invalid HTML code, which used to break the menu editor.
68
+ * Fixed a PHP warning that appeared when using this plugin together with WooCommerce or YITH WooCommerce Gift Cards and running PHP 7.1.
69
+ * Minor performance improvements.
70
+ * Tested with WP 4.8.3 and 4.9.
71
+
72
  = 1.8 =
73
  * You can edit plugin names and descriptions through the "Plugins" tab. This only changes how plugins are displayed on the "Plugins" page. It doesn't affect plugin files on disk.
74
  * Added an option to highlight new menu items. This feature is off by default. You can enable it in the "Settings" tab.