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