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.