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 | |
| 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.
|
