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 | 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 +14 -0
- images/copy-permissions.png +0 -0
- includes/editor-page.php +11 -39
- includes/menu-editor-core.php +52 -7
- includes/menu-item.php +50 -23
- js/menu-editor.js +108 -9
- js/menu-highlight-fix.js +10 -2
- menu-editor.php +1 -1
- readme.txt +11 -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 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
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 =
|
2011 |
|
2012 |
//The current URL must have as few extra parameters as possible.
|
2013 |
-
$extra_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::
|
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 |
-
* -
|
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 = (
|
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 |
-
|
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.
|
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
|
186 |
-
var shouldCloseOtherMenus = $('
|
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.
|
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.
|
7 |
-
Stable tag: 1.4.
|
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.
|