Admin Menu Editor - Version 1.4.2

Version Description

  • Tested on WP 4.1 and 4.2-alpha.
  • Fixed a bug that allowed Administrators to bypass custom permissions for the "Appearance -> Customize" menu item.
  • Fixed a regression in the menu highlighting algorithm.
  • Fixed an "array to string conversion" notice caused by passing array data in the query string.
  • Fixed menu scrolling occasionally not working when the user moved an item from one menu to another, much larger menu (e.g. having 20+ submenu items).
  • Fixed a bug where moving a submenu item from a plugin menu that doesn't have a hook callback (i.e. an unusable menu serving as a placeholder) to a different menu would corrupt the menu item URL.
  • Other minor bug fixes.
Download this release

Release Info

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

Code changes from version 1.4.1 to 1.4.2

css/menu-editor.css CHANGED
@@ -870,6 +870,10 @@ select.ws_dropdown optgroup option {
870
padding-top: 25px;
871
}
872
873
.ws_dont_show_again {
874
display: inline-block;
875
margin-top: 1em;
@@ -1054,6 +1058,16 @@ select.ws_dropdown optgroup option {
1054
margin-left: 0.5em;
1055
}
1056
1057
#ws_sidebar_pro_ad {
1058
min-width: 225px;
1059
870
padding-top: 25px;
871
}
872
873
+ #ws_import_error_response {
874
+ width: 100%;
875
+ }
876
+
877
.ws_dont_show_again {
878
display: inline-block;
879
margin-top: 1em;
1058
margin-left: 0.5em;
1059
}
1060
1061
+
1062
+ /************************************
1063
+ Copy Permissions dialog
1064
+ *************************************/
1065
+ #ws-ame-copy-permissions-dialog select {
1066
+ min-width: 280px;
1067
+ }
1068
+
1069
+
1070
+
1071
#ws_sidebar_pro_ad {
1072
min-width: 225px;
1073
images/copy-permissions.png ADDED
Binary file
includes/editor-page.php CHANGED
@@ -14,6 +14,7 @@ $icons = array(
14
'delete' => '/page-delete.png',
15
'new-separator' => '/separator-add.png',
16
'toggle-all' => '/check-all.png',
17
);
18
foreach($icons as $name => $url) {
19
$icons[$name] = $images_url . $url;
@@ -43,50 +44,18 @@ if ( !apply_filters('admin_menu_editor_is_pro', false) ){
43
</h2>
44
45
<?php
46
- if ( !empty($_GET['message']) ){
47
- if ( intval($_GET['message']) == 1 ){
48
- echo '<div id="message" class="updated fade"><p><strong>Settings saved.</strong></p></div>';
49
- } elseif ( intval($_GET['message']) == 2 ) {
50
- echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
51
- }
52
}
53
- ?>
54
-
55
- <?php
56
- $hint_id = 'ws_whats_new_120';
57
- $show_whats_new = false && apply_filters('admin_menu_editor_is_pro', false) && !empty($editor_data['show_hints'][$hint_id]);
58
- if ( $show_whats_new ):
59
- ?>
60
- <div class="ws_hint" id="<?php echo esc_attr($hint_id); ?>">
61
- <div class="ws_hint_close" title="Close">x</div>
62
- <div class="ws_hint_content">
63
- <strong>What's New In 1.20 and 1.30</strong>
64
- <ul>
65
- <li>New menu permissions interface.
66
- <a href="http://w-shadow.com/admin-menu-editor-pro/permissions/">Learn more.</a></li>
67
-
68
- <li>You can now use "not:user:username", "capability1,capability2", "capability1+capability2" and other
69
- advanced syntax in the capability field. See the link above for details.</li>
70
-
71
- <li>You can drag sub-menu items to the top level and the other way around. To do it,
72
- drag the item to the very end of the (sub-)menu and drop it on the yellow rectangle that will appear.</li>
73
-
74
- <li>Added a "Target page" drop-down to simplify setting menu URLs. You can still enter an arbitrary URL
75
- by selecting "Custom".</li>
76
-
77
- <li>Miscellaneous bug fixes.</li>
78
-
79
- </ul>
80
- </div>
81
- </div>
82
- <?php
83
- endif;
84
- ?>
85
86
- <?php
87
include dirname(__FILE__) . '/access-editor-dialog.php';
88
if ( apply_filters('admin_menu_editor_is_pro', false) ) {
89
include dirname(__FILE__) . '/../extras/menu-color-dialog.php';
90
}
91
?>
92
@@ -126,6 +95,9 @@ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
126
127
<a id='ws_toggle_all_menus' class='ws_button' href='javascript:void(0)'
128
title='Toggle all menus for the selected role'><img src='<?php echo $icons['toggle-all']; ?>' alt="Toggle all" /></a>
129
<?php endif; ?>
130
131
<div class="clear"></div>
14
'delete' => '/page-delete.png',
15
'new-separator' => '/separator-add.png',
16
'toggle-all' => '/check-all.png',
17
+ 'copy-permissions' => '/copy-permissions.png',
18
);
19
foreach($icons as $name => $url) {
20
$icons[$name] = $images_url . $url;
44
</h2>
45
46
<?php
47
+ if ( !empty($_GET['message']) ){
48
+ if ( intval($_GET['message']) == 1 ){
49
+ echo '<div id="message" class="updated fade"><p><strong>Settings saved.</strong></p></div>';
50
+ } elseif ( intval($_GET['message']) == 2 ) {
51
+ echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
52
}
53
+ }
54
55
include dirname(__FILE__) . '/access-editor-dialog.php';
56
if ( apply_filters('admin_menu_editor_is_pro', false) ) {
57
include dirname(__FILE__) . '/../extras/menu-color-dialog.php';
58
+ include dirname(__FILE__) . '/../extras/copy-permissions-dialog.php';
59
}
60
?>
61
95
96
<a id='ws_toggle_all_menus' class='ws_button' href='javascript:void(0)'
97
title='Toggle all menus for the selected role'><img src='<?php echo $icons['toggle-all']; ?>' alt="Toggle all" /></a>
98
+
99
+ <a id='ws_copy_role_permissions' class='ws_button' href='javascript:void(0)'
100
+ title='Copy all menu permissions from one role to another'><img src='<?php echo $icons['copy-permissions']; ?>' alt="Copy permissions" /></a>
101
<?php endif; ?>
102
103
<div class="clear"></div>
includes/menu-editor-core.php CHANGED
@@ -1076,9 +1076,6 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1076
$new_submenu = array();
1077
$this->title_lookups = array();
1078
1079
- //Sort the menu by position
1080
- uasort($tree, 'ameMenuItem::compare_position');
1081
-
1082
//Prepare the top menu
1083
$first_nonseparator_found = false;
1084
foreach ($tree as $topmenu){
@@ -1107,8 +1104,6 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1107
$has_submenu_icons = false;
1108
if( !empty($topmenu['items']) ){
1109
$items = $topmenu['items'];
1110
- //Sort by position
1111
- uasort($items, 'ameMenuItem::compare_position');
1112
1113
foreach ($items as $item) {
1114
//Skip missing and hidden items
@@ -1125,6 +1120,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1125
//Keep track of which menus have items with icons.
1126
$has_submenu_icons = $has_submenu_icons || !empty($item['has_submenu_icon']);
1127
}
1128
}
1129
1130
//The ame-has-submenu-icons class lets us change the appearance of all submenu items at once,
@@ -1137,6 +1135,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1137
$new_tree[] = $topmenu;
1138
}
1139
1140
//Use only the highest-priority capability for each URL.
1141
foreach($this->page_access_lookup as $url => $capabilities) {
1142
ksort($capabilities);
@@ -2006,11 +2007,17 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2006
}
2007
}
2008
2009
//The current URL must match all query parameters of the item URL.
2010
- $different_params = array_diff_assoc($item_url['params'], $current_url['params']);
2011
2012
//The current URL must have as few extra parameters as possible.
2013
- $extra_params = array_diff_assoc($current_url['params'], $item_url['params']);
2014
2015
if ( $is_close_match && (count($different_params) == 0) && (count($extra_params) < $best_extra_params) ) {
2016
$best_item = $item;
@@ -2064,6 +2071,44 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2064
return $parsed;
2065
}
2066
2067
/**
2068
* Check if two paths match. Intended for comparing WP admin URLs.
2069
*
1076
$new_submenu = array();
1077
$this->title_lookups = array();
1078
1079
//Prepare the top menu
1080
$first_nonseparator_found = false;
1081
foreach ($tree as $topmenu){
1104
$has_submenu_icons = false;
1105
if( !empty($topmenu['items']) ){
1106
$items = $topmenu['items'];
1107
1108
foreach ($items as $item) {
1109
//Skip missing and hidden items
1120
//Keep track of which menus have items with icons.
1121
$has_submenu_icons = $has_submenu_icons || !empty($item['has_submenu_icon']);
1122
}
1123
+
1124
+ //Sort by position
1125
+ uasort($new_items, 'ameMenuItem::compare_position');
1126
}
1127
1128
//The ame-has-submenu-icons class lets us change the appearance of all submenu items at once,
1135
$new_tree[] = $topmenu;
1136
}
1137
1138
+ //Sort the menu by position
1139
+ uasort($new_tree, 'ameMenuItem::compare_position');
1140
+
1141
//Use only the highest-priority capability for each URL.
1142
foreach($this->page_access_lookup as $url => $capabilities) {
1143
ksort($capabilities);
2007
}
2008
}
2009
2010
+ //Special case: In WP 4.0+ the URL of the "Customize" menu changes often due to a "return" query parameter
2011
+ //that contains the current page URL. To reliably recognize this item, we should ignore that parameter.
2012
+ if ( $this->endsWith($item_url['path'], 'customize.php') ) {
2013
+ unset($item_url['params']['return']);
2014
+ }
2015
+
2016
//The current URL must match all query parameters of the item URL.
2017
+ $different_params = $this->arrayDiffAssocRecursive($item_url['params'], $current_url['params']);
2018
2019
//The current URL must have as few extra parameters as possible.
2020
+ $extra_params = $this->arrayDiffAssocRecursive($current_url['params'], $item_url['params']);
2021
2022
if ( $is_close_match && (count($different_params) == 0) && (count($extra_params) < $best_extra_params) ) {
2023
$best_item = $item;
2071
return $parsed;
2072
}
2073
2074
+ /**
2075
+ * Get the difference of two arrays.
2076
+ *
2077
+ * This methods works like array_diff_assoc(), except it also supports nested arrays by comparing them recursively.
2078
+ *
2079
+ * @param array $array1 The base array.
2080
+ * @param array $array2 The array to compare to.
2081
+ * @return array An associative array of values from $array1 that are not present in $array2.
2082
+ */
2083
+ private function arrayDiffAssocRecursive($array1, $array2) {
2084
+ $difference = array();
2085
+
2086
+ foreach($array1 as $key => $value) {
2087
+ if ( !array_key_exists($key, $array2) ) {
2088
+ $difference[$key] = $value;
2089
+ continue;
2090
+ }
2091
+
2092
+ $otherValue = $array2[$key];
2093
+ if ( is_array($value) !== is_array($otherValue) ) {
2094
+ //If only one of the two values is an array then they can't be equal.
2095
+ $difference[$key] = $value;
2096
+ } elseif ( is_array($value) ) {
2097
+ //Compare array values recursively.
2098
+ $subDiff = $this->arrayDiffAssocRecursive($value, $otherValue);
2099
+ if( !empty($subDiff) ) {
2100
+ $difference[$key] = $subDiff;
2101
+ }
2102
+
2103
+ //Like the original array_diff_assoc(), we compare the values as strings.
2104
+ } elseif ( (string)$value !== (string)$array2[$key] ) {
2105
+ $difference[$key] = $value;
2106
+ }
2107
+ }
2108
+
2109
+ return $difference;
2110
+ }
2111
+
2112
/**
2113
* Check if two paths match. Intended for comparing WP admin URLs.
2114
*
includes/menu-item.php CHANGED
@@ -397,7 +397,7 @@ abstract class ameMenuItem {
397
}
398
399
if ( self::is_hook_or_plugin_page($menu_url, $parent_url) ) {
400
- $base_file = self::is_hook_or_plugin_page($parent_url) ? 'admin.php' : $parent_url;
401
$url = add_query_arg(array('page' => $menu_url), $base_file);
402
} else {
403
$url = $menu_url;
@@ -411,15 +411,23 @@ abstract class ameMenuItem {
411
}
412
$pageFile = self::remove_query_from($page_url);
413
414
/*
415
* Special case: Absolute paths.
416
*
417
* - add_submenu_page() applies plugin_basename() to the menu slug, so we don't need to worry about plugin
418
* paths. However, absolute paths that *don't* point point to the plugins directory can be a problem.
419
*
420
- * - If we blindly append $pageFile to another path, we'll get something like "C:\a\b/wp-admin/C:\c\d.php".
421
- * PHP 5.2.5 has a known bug where calling file_exists() on that kind of an invalid filename will cause
422
- * a timeout and a crash in some configurations. See: https://bugs.php.net/bug.php?id=44412
423
*
424
* - WP 3.9.2 and 4.0+ unintentionally break menu URLs like "foo.php?page=c:\a\b.php" because esc_url()
425
* interprets the part before the colon as an invalid protocol. As a result, such links have an empty URL
@@ -430,26 +438,8 @@ abstract class ameMenuItem {
430
* can still be used as unique slugs for menus with hook callbacks, so we shouldn't reject them outright.
431
* Related: https://core.trac.wordpress.org/ticket/10011
432
*/
433
- $allowPathConcatenation = (substr($pageFile, 1, 1) !== ':'); //Reject "C:\whatever" and similar.
434
-
435
- //Check our hard-coded list of admin pages first. It's measurably faster than
436
- //hitting the disk with is_file().
437
- if ( isset(self::$known_wp_admin_files[$pageFile]) ) {
438
- return false;
439
- }
440
-
441
- //Now actually check the filesystem.
442
- $adminFileExists = $allowPathConcatenation && is_file(ABSPATH . 'wp-admin/' . $pageFile);
443
- if ( $adminFileExists ) {
444
- return false;
445
- }
446
-
447
- $hasHook = (get_plugin_page_hook($page_url, $parent_page_url) !== null);
448
- if ( $hasHook ) {
449
- return true;
450
- }
451
452
- //Note: We don't need to call plugin_basename() on $pageFile because add_submenu_page() already did that.
453
$pluginFileExists = $allowPathConcatenation
454
&& ($page_url != 'index.php')
455
&& is_file(WP_PLUGIN_DIR . '/' . $pageFile);
@@ -460,6 +450,43 @@ abstract class ameMenuItem {
460
return false;
461
}
462
463
/**
464
* Check if a field is currently set to its default value.
465
*
397
}
398
399
if ( self::is_hook_or_plugin_page($menu_url, $parent_url) ) {
400
+ $base_file = self::is_wp_admin_file($parent_url) ? $parent_url : 'admin.php';
401
$url = add_query_arg(array('page' => $menu_url), $base_file);
402
} else {
403
$url = $menu_url;
411
}
412
$pageFile = self::remove_query_from($page_url);
413
414
+ //Files in /wp-admin are part of WP core so they're not plugin pages.
415
+ if ( self::is_wp_admin_file($pageFile) ) {
416
+ return false;
417
+ }
418
+
419
+ $hasHook = (get_plugin_page_hook($page_url, $parent_page_url) !== null);
420
+ if ( $hasHook ) {
421
+ return true;
422
+ }
423
+
424
/*
425
* Special case: Absolute paths.
426
*
427
* - add_submenu_page() applies plugin_basename() to the menu slug, so we don't need to worry about plugin
428
* paths. However, absolute paths that *don't* point point to the plugins directory can be a problem.
429
*
430
+ * - Due to a known PHP bug, certain invalid paths can crash PHP. See self::is_safe_to_append().
431
*
432
* - WP 3.9.2 and 4.0+ unintentionally break menu URLs like "foo.php?page=c:\a\b.php" because esc_url()
433
* interprets the part before the colon as an invalid protocol. As a result, such links have an empty URL
438
* can still be used as unique slugs for menus with hook callbacks, so we shouldn't reject them outright.
439
* Related: https://core.trac.wordpress.org/ticket/10011
440
*/
441
+ $allowPathConcatenation = self::is_safe_to_append($pageFile);
442
443
$pluginFileExists = $allowPathConcatenation
444
&& ($page_url != 'index.php')
445
&& is_file(WP_PLUGIN_DIR . '/' . $pageFile);
450
return false;
451
}
452
453
+ /**
454
+ * Check if a file exists inside the /wp-admin subdirectory.
455
+ *
456
+ * @param string $filename
457
+ * @return bool
458
+ */
459
+ private static function is_wp_admin_file($filename) {
460
+ //Check our hard-coded list of admin pages first. It's measurably faster than
461
+ //hitting the disk with is_file().
462
+ if ( isset(self::$known_wp_admin_files[$filename]) ) {
463
+ return self::$known_wp_admin_files[$filename];
464
+ }
465
+
466
+ //Now actually check the filesystem.
467
+ $adminFileExists = self::is_safe_to_append($filename)
468
+ && is_file(ABSPATH . 'wp-admin/' . $filename);
469
+
470
+ //Cache the result for later. We can generally expect more than one call per top level menu URL.
471
+ self::$known_wp_admin_files[$filename] = $adminFileExists;
472
+
473
+ return $adminFileExists;
474
+ }
475
+
476
+ /**
477
+ * Verify that it's safe to append a given filename to another path.
478
+ *
479
+ * If we blindly append an absolute path to another path, we can get something like "C:\a\b/wp-admin/C:\c\d.php".
480
+ * PHP 5.2.5 has a known bug where calling file_exists() on that kind of an invalid filename will cause
481
+ * a timeout and a crash in some configurations. See: https://bugs.php.net/bug.php?id=44412
482
+ *
483
+ * @param string $filename
484
+ * @return bool
485
+ */
486
+ private static function is_safe_to_append($filename) {
487
+ return (substr($filename, 1, 1) !== ':'); //Reject "C:\whatever" and similar.
488
+ }
489
+
490
/**
491
* Check if a field is currently set to its default value.
492
*
js/menu-editor.js CHANGED
@@ -144,6 +144,8 @@ var AmeCapabilityManager = (function(roles, users) {
144
case 'user':
145
specificity = 10;
146
break;
147
}
148
return specificity;
149
};
@@ -203,11 +205,7 @@ var itemTemplates = {
203
*/
204
function setInputValue(input, value) {
205
if (input.attr('type') == 'checkbox'){
206
- if (value){
207
- input.attr('checked', 'checked');
208
- } else {
209
- input.removeAttr('checked');
210
- }
211
} else {
212
input.val(value);
213
}
@@ -1633,7 +1631,7 @@ $(document).ready(function(){
1633
1634
var actorHasAccess = actorCanAccessMenu(menuItem, actor);
1635
if (actorHasAccess) {
1636
- checkbox.attr('checked', 'checked');
1637
}
1638
1639
alternate = (alternate == '') ? 'alternate' : '';
@@ -2429,6 +2427,82 @@ $(document).ready(function(){
2429
});
2430
});
2431
2432
/*************************************************************************
2433
Item toolbar buttons
2434
*************************************************************************/
@@ -2758,7 +2832,8 @@ $(document).ready(function(){
2758
});
2759
2760
$('#ws_import_menu').click(function(){
2761
- $('#import_progress_notice, #import_progress_notice2, #import_complete_notice').hide();
2762
$('#import_menu_form').resetForm();
2763
//The "Upload" button is disabled until the user selects a file
2764
$('#ws_start_import').attr('disabled', 'disabled');
@@ -2772,6 +2847,21 @@ $(document).ready(function(){
2772
$('#ws_start_import').prop('disabled', ! $(this).val() );
2773
});
2774
2775
//AJAXify the upload form
2776
$('#import_menu_form').ajaxForm({
2777
dataType : 'json',
@@ -2793,7 +2883,9 @@ $(document).ready(function(){
2793
$('#ws_start_import').attr('disabled', 'disabled');
2794
return true;
2795
},
2796
- success: function(data){
2797
var importDialog = $('#import_dialog');
2798
if ( !importDialog.dialog('isOpen') ){
2799
//Whoops, the user closed the dialog while the upload was in progress.
@@ -2801,13 +2893,17 @@ $(document).ready(function(){
2801
return;
2802
}
2803
2804
if ( typeof data['error'] != 'undefined' ){
2805
alert(data.error);
2806
//Let the user try again
2807
$('#import_menu_form').resetForm();
2808
importDialog.find('.hide-when-uploading').show();
2809
}
2810
- $('#import_progress_notice').hide();
2811
2812
if ( (typeof data['tree'] != 'undefined') && data.tree ){
2813
//Whee, we got back a (seemingly) valid menu. A veritable miracle!
@@ -2823,6 +2919,9 @@ $(document).ready(function(){
2823
}), 500);
2824
}
2825
2826
}
2827
});
2828
144
case 'user':
145
specificity = 10;
146
break;
147
+ default:
148
+ specificity = 0;
149
}
150
return specificity;
151
};
205
*/
206
function setInputValue(input, value) {
207
if (input.attr('type') == 'checkbox'){
208
+ input.prop('checked', value);
209
} else {
210
input.val(value);
211
}
1631
1632
var actorHasAccess = actorCanAccessMenu(menuItem, actor);
1633
if (actorHasAccess) {
1634
+ checkbox.prop('checked', true);
1635
}
1636
1637
alternate = (alternate == '') ? 'alternate' : '';
2427
});
2428
});
2429
2430
+ //Copy all menu permissions from one role to another.
2431
+ var copyPermissionsDialog = $('#ws-ame-copy-permissions-dialog').dialog({
2432
+ autoOpen: false,
2433
+ modal: true,
2434
+ closeText: ' ',
2435
+ draggable: false
2436
+ });
2437
+
2438
+ //Populate source/destination lists.
2439
+ var sourceActorList = $('#ame-copy-source-actor'), destinationActorList = $('#ame-copy-destination-actor');
2440
+ $.each(wsEditorData.actors, function(actor, name) {
2441
+ var option = $('<option>', {val: actor, text: name});
2442
+ sourceActorList.append(option);
2443
+ destinationActorList.append(option.clone());
2444
+ });
2445
+
2446
+ //The "Copy permissions" toolbar button.
2447
+ $('#ws_copy_role_permissions').click(function() {
2448
+ //Pre-select the current actor as the destination.
2449
+ if (selectedActor !== null) {
2450
+ destinationActorList.val(selectedActor);
2451
+ }
2452
+ copyPermissionsDialog.dialog('open');
2453
+ });
2454
+
2455
+ //Actually copy the permissions when the user click the confirmation button.
2456
+ var copyConfirmationButton = $('#ws-ame-confirm-copy-permissions');
2457
+ copyConfirmationButton.click(function() {
2458
+ var sourceActor = sourceActorList.val();
2459
+ var destinationActor = destinationActorList.val();
2460
+
2461
+ if (sourceActor === null || destinationActor === null) {
2462
+ alert('Select a source and a destination first.');
2463
+ return;
2464
+ }
2465
+
2466
+ //Iterate over all menu items and copy the permissions from one actor to the other.
2467
+ var allMenuNodes = $('.ws_menu', '#ws_menu_box').add('.ws_item', '#ws_submenu_box');
2468
+ allMenuNodes.each(function() {
2469
+ var node = $(this);
2470
+ var menuItem = node.data('menu_item');
2471
+
2472
+ //Only change permissions when they don't match. This ensures we won't unnecessarily overwrite default
2473
+ //permissions and bloat the configuration with extra grant_access entries.
2474
+ var sourceAccess = actorCanAccessMenu(menuItem, sourceActor);
2475
+ var destinationAccess = actorCanAccessMenu(menuItem, destinationActor);
2476
+ if (sourceAccess !== destinationAccess) {
2477
+ setActorAccess(node, destinationActor, sourceAccess);
2478
+ //Note: In theory, we could also look at the default permissions for destinationActor and
2479
+ //revert to default instead of overwriting if that would make the two actors' permissions match.
2480
+ }
2481
+ });
2482
+
2483
+ //If the user is currently looking at the destination actor, force the UI to refresh
2484
+ //so that they can see the new permissions.
2485
+ if (selectedActor === destinationActor) {
2486
+ //This is a bit of a hack, but right now there's no better way to refresh all items at once.
2487
+ setSelectedActor(null);
2488
+ setSelectedActor(destinationActor);
2489
+ }
2490
+
2491
+ //All done.
2492
+ copyPermissionsDialog.dialog('close');
2493
+ });
2494
+
2495
+ //Only enable the copy button when the user selects a valid source and destination.
2496
+ copyConfirmationButton.prop('disabled', true);
2497
+ sourceActorList.add(destinationActorList).click(function() {
2498
+ var sourceActor = sourceActorList.val();
2499
+ var destinationActor = destinationActorList.val();
2500
+
2501
+ var validInputs = (sourceActor !== null) && (destinationActor !== null) && (sourceActor !== destinationActor);
2502
+ copyConfirmationButton.prop('disabled', !validInputs);
2503
+ });
2504
+
2505
+
2506
/*************************************************************************
2507
Item toolbar buttons
2508
*************************************************************************/
2832
});
2833
2834
$('#ws_import_menu').click(function(){
2835
+ $('#import_progress_notice, #import_progress_notice2, #import_complete_notice, #ws_import_error').hide();
2836
+ $('#ws_import_panel').show();
2837
$('#import_menu_form').resetForm();
2838
//The "Upload" button is disabled until the user selects a file
2839
$('#ws_start_import').attr('disabled', 'disabled');
2847
$('#ws_start_import').prop('disabled', ! $(this).val() );
2848
});
2849
2850
+ //This function displays unhandled server side errors. In theory, our upload handler always returns a well-formed
2851
+ //response even if there's an error. In practice, stuff can go wrong in unexpected ways (e.g. plugin conflicts).
2852
+ function handleUnexpectedImportError(xhr, errorMessage) {
2853
+ //The server-side code didn't catch this error, so it's probably something serious
2854
+ //and retrying won't work.
2855
+ $('#import_menu_form').resetForm();
2856
+ $('#ws_import_panel').hide();
2857
+
2858
+ //Display error information.
2859
+ $('#ws_import_error_message').text(errorMessage);
2860
+ $('#ws_import_error_http_code').text(xhr.status);
2861
+ $('#ws_import_error_response').text((xhr.responseText !== '') ? xhr.responseText : '[Empty response]');
2862
+ $('#ws_import_error').show();
2863
+ }
2864
+
2865
//AJAXify the upload form
2866
$('#import_menu_form').ajaxForm({
2867
dataType : 'json',
2883
$('#ws_start_import').attr('disabled', 'disabled');
2884
return true;
2885
},
2886
+ success: function(data, status, xhr) {
2887
+ $('#import_progress_notice').hide();
2888
+
2889
var importDialog = $('#import_dialog');
2890
if ( !importDialog.dialog('isOpen') ){
2891
//Whoops, the user closed the dialog while the upload was in progress.
2893
return;
2894
}
2895
2896
+ if ( data === null ) {
2897
+ handleUnexpectedImportError(xhr, 'Invalid response from server. Please check your PHP error log.');
2898
+ return;
2899
+ }
2900
+
2901
if ( typeof data['error'] != 'undefined' ){
2902
alert(data.error);
2903
//Let the user try again
2904
$('#import_menu_form').resetForm();
2905
importDialog.find('.hide-when-uploading').show();
2906
}
2907
2908
if ( (typeof data['tree'] != 'undefined') && data.tree ){
2909
//Whee, we got back a (seemingly) valid menu. A veritable miracle!
2919
}), 500);
2920
}
2921
2922
+ },
2923
+ error: function(xhr, status, errorMessage) {
2924
+ handleUnexpectedImportError(xhr, errorMessage);
2925
}
2926
});
2927
js/menu-highlight-fix.js CHANGED
@@ -182,8 +182,8 @@ jQuery(function($) {
182
(otherHighlightedMenus.length > 0);
183
184
if (isWrongMenuHighlighted) {
185
- //Account for users who use a plugin to keep all menus expanded.
186
- var shouldCloseOtherMenus = $('li.wp-has-current-submenu', '#adminmenu').length <= 1;
187
if (shouldCloseOtherMenus) {
188
otherHighlightedMenus
189
.add('> a', otherHighlightedMenus)
@@ -196,6 +196,14 @@ jQuery(function($) {
196
if (parentMenu.hasClass('wp-has-submenu')) {
197
parentMenuAndLink.addClass('wp-has-current-submenu wp-menu-open');
198
}
199
}
200
201
if (isWrongItemHighlighted) {
182
(otherHighlightedMenus.length > 0);
183
184
if (isWrongMenuHighlighted) {
185
+ //Account for users who use the Expanded Admin Menus plugin to keep all menus expanded.
186
+ var shouldCloseOtherMenus = ! $('div.expand-arrow', '#adminmenu').get(0);
187
if (shouldCloseOtherMenus) {
188
otherHighlightedMenus
189
.add('> a', otherHighlightedMenus)
196
if (parentMenu.hasClass('wp-has-submenu')) {
197
parentMenuAndLink.addClass('wp-has-current-submenu wp-menu-open');
198
}
199
+
200
+ //Note: WordPress switches the admin menu between `position: fixed` and `position: relative` depending on
201
+ //how tall it is compared to the browser window. Opening a different submenu can change the menu's height,
202
+ //so we must trigger the position update to avoid bugs. If we don't, we can end up with a very tall menu
203
+ //that's not scrollable (due to being stuck with `position: fixed`).
204
+ if ((typeof window['stickyMenu'] === 'object') && (typeof window['stickyMenu']['update'] === 'function')) {
205
+ window.stickyMenu.update();
206
+ }
207
}
208
209
if (isWrongItemHighlighted) {
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.4.1
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.4.2
7
Author: Janis Elsts
8
Author URI: http://w-shadow.com/blog/
9
*/
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: 3.8
6
- Tested up to: 4.0
7
- Stable tag: 1.4.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,6 +63,15 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
64
== Changelog ==
65
66
= 1.4.1 =
67
* Fixed "Appearance -> Customize" always showing up as "new" and ignoring custom settings.
68
* Fixed a WooCommerce 2.2.1+ compatibility issue that caused a superfluous "WooCommerce -> WooCommerce" submenu item to show up. Normally this item is invisible.
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: 3.8
6
+ Tested up to: 4.1
7
+ Stable tag: 1.4.2
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.4.2 =
67
+ * Tested on WP 4.1 and 4.2-alpha.
68
+ * Fixed a bug that allowed Administrators to bypass custom permissions for the "Appearance -> Customize" menu item.
69
+ * Fixed a regression in the menu highlighting algorithm.
70
+ * Fixed an "array to string conversion" notice caused by passing array data in the query string.
71
+ * Fixed menu scrolling occasionally not working when the user moved an item from one menu to another, much larger menu (e.g. having 20+ submenu items).
72
+ * Fixed a bug where moving a submenu item from a plugin menu that doesn't have a hook callback (i.e. an unusable menu serving as a placeholder) to a different menu would corrupt the menu item URL.
73
+ * Other minor bug fixes.
74
+
75
= 1.4.1 =
76
* Fixed "Appearance -> Customize" always showing up as "new" and ignoring custom settings.
77
* Fixed a WooCommerce 2.2.1+ compatibility issue that caused a superfluous "WooCommerce -> WooCommerce" submenu item to show up. Normally this item is invisible.