Admin Menu Editor - Version 1.7.1

Version Description

  • Split the "required capability" field into two parts - a read-only field that shows the actual required capability, and an editable "extra capability" that you can use to restrict access to the menu.
  • Added more detailed permission error messages. You can turn them off in the "Settings" tab by changing "Error verbosity level" to "Low".
  • Tested up to WP 4.6.
Download this release

Release Info

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

Code changes from version 1.7 to 1.7.1

css/menu-editor.css CHANGED
@@ -1,8 +1,6 @@
1
/* Admin Menu Editor CSS file */
2
#ws_menu_editor {
3
min-width: 780px; }
4
- #ws_menu_editor #ws_actor_selector {
5
- margin-top: 6px; }
6
7
.ame-is-free-version #ws_menu_editor {
8
margin-top: 9px; }
@@ -65,7 +63,7 @@
65
margin-bottom: -1px; }
66
67
#ws_actor_selector {
68
- margin-top: 5px; }
69
70
/**
71
* The checkbox that lets the user show/hide a menu for the currently selected actor.
@@ -513,6 +511,36 @@ select.ws_dropdown option {
513
select.ws_dropdown optgroup option {
514
padding-left: 10px; }
515
516
/************************************
517
Icon selector
518
*************************************/
@@ -521,11 +549,11 @@ select.ws_dropdown optgroup option {
521
border-radius: 3px;
522
background-color: white;
523
width: 216px;
524
- padding: 2px;
525
position: absolute; }
526
527
#ws_icon_selector.ws_with_more_icons {
528
- width: 540px; }
529
530
#ws_icon_selector .ws_icon_extra {
531
display: none; }
@@ -613,6 +641,17 @@ select.ws_dropdown optgroup option {
613
width: 16px;
614
height: 16px; }
615
616
/* MP6 admin style compatibility */
617
#ws_icon_selector .ws_icon_option .icon16::before {
618
margin: 0;
@@ -625,11 +664,6 @@ select.ws_dropdown optgroup option {
625
#ws_choose_icon_from_media {
626
margin: 2px; }
627
628
- #ws_show_more_icons {
629
- margin: 2px;
630
- height: 30px;
631
- width: 68px; }
632
-
633
/************************************
634
Embedded page selector
635
*************************************/
@@ -976,8 +1010,7 @@ a#ws-ame-delete-color-preset:hover {
976
background: white;
977
filter: alpha(opacity=60);
978
opacity: 0.6;
979
- -moz-opacity: 0.6;
980
- -khtml-opacity: 0.6; }
981
982
#ws_role_access_overlay_content {
983
position: absolute;
@@ -1181,7 +1214,7 @@ a#ws-ame-delete-color-preset:hover {
1181
padding-left: 6px; }
1182
#ws_user_selection_panels #ws_selected_users .ws_user_display_name_column {
1183
display: none; }
1184
- #ws_user_selection_panels #ws_selected_users tr.ws_is_current_user .ws_user_action_button {
1185
display: none; }
1186
#ws_user_selection_panels #ws_selected_users_caption {
1187
font-size: 14px;
@@ -1212,7 +1245,7 @@ a#ws-ame-delete-color-preset:hover {
1212
/************************************
1213
Tooltips and hints
1214
*************************************/
1215
- .ws_tooltip_trigger {
1216
cursor: pointer; }
1217
1218
.ws_tooltip_content_list {
@@ -1226,6 +1259,14 @@ a#ws-ame-delete-color-preset:hover {
1226
border-radius: 3px;
1227
max-width: 300px; }
1228
1229
#ws_plugin_settings_form .ws_tooltip_trigger .dashicons {
1230
font-size: 18px; }
1231
@@ -1267,6 +1308,28 @@ a#ws-ame-delete-color-preset:hover {
1267
list-style-position: inside;
1268
margin-left: 0.5em; }
1269
1270
/************************************
1271
Copy Permissions dialog
1272
*************************************/
1
/* Admin Menu Editor CSS file */
2
#ws_menu_editor {
3
min-width: 780px; }
4
5
.ame-is-free-version #ws_menu_editor {
6
margin-top: 9px; }
63
margin-bottom: -1px; }
64
65
#ws_actor_selector {
66
+ margin-top: 6px; }
67
68
/**
69
* The checkbox that lets the user show/hide a menu for the currently selected actor.
511
select.ws_dropdown optgroup option {
512
padding-left: 10px; }
513
514
+ /************************************
515
+ Tabs (small)
516
+ ************************************
517
+ Tabbed navigation for dropdowns and small dialogs.
518
+ */
519
+ .ws_tool_tab_nav {
520
+ list-style: outside none none;
521
+ padding: 0;
522
+ margin: 0 0 0 6px; }
523
+ .ws_tool_tab_nav li {
524
+ display: inline-block;
525
+ border: 1px solid transparent;
526
+ border-bottom-width: 0;
527
+ padding: 3px 5px 5px;
528
+ line-height: 1.35em;
529
+ margin-bottom: 0; }
530
+ .ws_tool_tab_nav li.ui-tabs-active {
531
+ border-color: #dfdfdf;
532
+ background-color: #FDFDFD;
533
+ border-bottom-color: #FDFDFD; }
534
+ .ws_tool_tab_nav a {
535
+ text-decoration: none; }
536
+ .ws_tool_tab_nav li.ui-tabs-active a {
537
+ color: #32373C; }
538
+
539
+ .ws_tool_tab {
540
+ border-top: 1px solid #DFDFDF;
541
+ margin-top: -1px;
542
+ background-color: #FDFDFD; }
543
+
544
/************************************
545
Icon selector
546
*************************************/
549
border-radius: 3px;
550
background-color: white;
551
width: 216px;
552
+ padding: 4px 0 0 0;
553
position: absolute; }
554
555
#ws_icon_selector.ws_with_more_icons {
556
+ width: 570px; }
557
558
#ws_icon_selector .ws_icon_extra {
559
display: none; }
641
width: 16px;
642
height: 16px; }
643
644
+ #ws_icon_selector .ws_tool_tab_nav {
645
+ display: inline-block;
646
+ margin-top: 2px;
647
+ position: relative; }
648
+ #ws_icon_selector .ws_tool_tab_nav li {
649
+ padding: 4px 10px 11px; }
650
+ #ws_icon_selector .ws_tool_tab {
651
+ padding: 4px 4px 2px;
652
+ max-height: 324px;
653
+ overflow-y: auto; }
654
+
655
/* MP6 admin style compatibility */
656
#ws_icon_selector .ws_icon_option .icon16::before {
657
margin: 0;
664
#ws_choose_icon_from_media {
665
margin: 2px; }
666
667
/************************************
668
Embedded page selector
669
*************************************/
1010
background: white;
1011
filter: alpha(opacity=60);
1012
opacity: 0.6;
1013
+ -moz-opacity: 0.6; }
1014
1015
#ws_role_access_overlay_content {
1016
position: absolute;
1214
padding-left: 6px; }
1215
#ws_user_selection_panels #ws_selected_users .ws_user_display_name_column {
1216
display: none; }
1217
+ #ws_user_selection_panels #ws_selected_users tr.ws_user_must_be_selected .ws_user_action_button {
1218
display: none; }
1219
#ws_user_selection_panels #ws_selected_users_caption {
1220
font-size: 14px;
1245
/************************************
1246
Tooltips and hints
1247
*************************************/
1248
+ .ws_tooltip_trigger, .ws_field_tooltip_trigger {
1249
cursor: pointer; }
1250
1251
.ws_tooltip_content_list {
1259
border-radius: 3px;
1260
max-width: 300px; }
1261
1262
+ .ws_field_tooltip_trigger .dashicons {
1263
+ font-size: 16px;
1264
+ height: 16px;
1265
+ vertical-align: bottom; }
1266
+
1267
+ .ws_field_tooltip_trigger {
1268
+ color: #a1a1a1; }
1269
+
1270
#ws_plugin_settings_form .ws_tooltip_trigger .dashicons {
1271
font-size: 18px; }
1272
1308
list-style-position: inside;
1309
margin-left: 0.5em; }
1310
1311
+ .ws_ame_doc_box .hndle {
1312
+ cursor: default !important; }
1313
+ .ws_ame_doc_box ul {
1314
+ list-style: disc outside;
1315
+ margin-left: 1em; }
1316
+ .ws_ame_doc_box li > ul {
1317
+ margin-top: 6px; }
1318
+ .ws_ame_doc_box .button-link .toggle-indicator::before {
1319
+ margin-top: 4px;
1320
+ width: 20px;
1321
+ -webkit-border-radius: 50%;
1322
+ border-radius: 50%;
1323
+ text-indent: -1px;
1324
+ content: "\f142";
1325
+ display: inline-block;
1326
+ font: normal 20px/1 dashicons;
1327
+ -webkit-font-smoothing: antialiased;
1328
+ -moz-osx-font-smoothing: grayscale;
1329
+ text-decoration: none !important; }
1330
+ .ws_ame_doc_box.closed .button-link .toggle-indicator::before {
1331
+ content: "\f140"; }
1332
+
1333
/************************************
1334
Copy Permissions dialog
1335
*************************************/
css/menu-editor.scss CHANGED
@@ -2,10 +2,6 @@
2
3
#ws_menu_editor {
4
min-width: 780px;
5
-
6
- #ws_actor_selector {
7
- margin-top: 6px;
8
- }
9
}
10
11
.ame-is-free-version #ws_menu_editor {
@@ -97,7 +93,7 @@ $mainContainerBorderWidth: 1px;
97
}
98
99
#ws_actor_selector {
100
- margin-top: 5px;
101
}
102
103
/**
@@ -706,6 +702,53 @@ select.ws_dropdown optgroup option {
706
padding-left: 10px;
707
}
708
709
/************************************
710
Icon selector
711
*************************************/
@@ -716,12 +759,12 @@ select.ws_dropdown optgroup option {
716
background-color: white;
717
718
width: 216px;
719
- padding: 2px;
720
position: absolute;
721
}
722
723
#ws_icon_selector.ws_with_more_icons {
724
- width: 540px;
725
}
726
727
#ws_icon_selector .ws_icon_extra {
@@ -828,6 +871,28 @@ select.ws_dropdown optgroup option {
828
height: 16px;
829
}
830
831
/* MP6 admin style compatibility */
832
#ws_icon_selector .ws_icon_option .icon16::before {
833
margin: 0;
@@ -842,12 +907,6 @@ select.ws_dropdown optgroup option {
842
margin: 2px;
843
}
844
845
- #ws_show_more_icons {
846
- margin: 2px;
847
- height: 30px;
848
- width: 68px;
849
- }
850
-
851
/************************************
852
Embedded page selector
853
*************************************/
@@ -1294,7 +1353,6 @@ a#ws-ame-delete-color-preset:hover {
1294
filter: alpha(opacity=60);
1295
opacity: 0.6;
1296
-moz-opacity:0.6;
1297
- -khtml-opacity: 0.6;
1298
}
1299
1300
#ws_role_access_overlay_content {
@@ -1639,8 +1697,8 @@ $userSelectionPanelPadding: 10px;
1639
display: none;
1640
}
1641
1642
- //You can't deselect the current user. It always stays in the actor list.
1643
- tr.ws_is_current_user {
1644
.ws_user_action_button {
1645
display: none;
1646
}
@@ -1699,7 +1757,7 @@ $userSelectionPanelPadding: 10px;
1699
Tooltips and hints
1700
*************************************/
1701
1702
- .ws_tooltip_trigger {
1703
cursor: pointer;
1704
}
1705
@@ -1716,6 +1774,16 @@ $userSelectionPanelPadding: 10px;
1716
max-width: 300px;
1717
}
1718
1719
//Tooltips on the settings page.
1720
#ws_plugin_settings_form .ws_tooltip_trigger .dashicons {
1721
font-size: 18px;
@@ -1768,6 +1836,40 @@ $userSelectionPanelPadding: 10px;
1768
margin-left: 0.5em;
1769
}
1770
1771
1772
/************************************
1773
Copy Permissions dialog
2
3
#ws_menu_editor {
4
min-width: 780px;
5
}
6
7
.ame-is-free-version #ws_menu_editor {
93
}
94
95
#ws_actor_selector {
96
+ margin-top: 6px;
97
}
98
99
/**
702
padding-left: 10px;
703
}
704
705
+ /************************************
706
+ Tabs (small)
707
+ ************************************
708
+ Tabbed navigation for dropdowns and small dialogs.
709
+ */
710
+
711
+ $activeToolTabBackground: #FDFDFD;
712
+
713
+ .ws_tool_tab_nav {
714
+ list-style: outside none none;
715
+ padding: 0;
716
+ margin: 0 0 0 6px;
717
+
718
+ li {
719
+ display: inline-block;
720
+
721
+ border: 1px solid transparent;
722
+ border-bottom-width: 0;
723
+ padding: 3px 5px 5px;
724
+ line-height: 1.35em;
725
+
726
+ margin-bottom: 0;
727
+ }
728
+
729
+ li.ui-tabs-active {
730
+ border-color: #dfdfdf;
731
+ background-color: #FDFDFD;
732
+ border-bottom-color: $activeToolTabBackground;
733
+ }
734
+
735
+ a {
736
+ text-decoration: none;
737
+ }
738
+
739
+ li.ui-tabs-active a {
740
+ color: #32373C;
741
+ }
742
+ }
743
+
744
+ .ws_tool_tab {
745
+ border-top: 1px solid #DFDFDF;
746
+ margin-top: -1px;
747
+ background-color: $activeToolTabBackground;
748
+
749
+ //Suggestion: Use 12px inner padding like in post editor boxes.
750
+ }
751
+
752
/************************************
753
Icon selector
754
*************************************/
759
background-color: white;
760
761
width: 216px;
762
+ padding: 4px 0 0 0;
763
position: absolute;
764
}
765
766
#ws_icon_selector.ws_with_more_icons {
767
+ width: 570px;
768
}
769
770
#ws_icon_selector .ws_icon_extra {
871
height: 16px;
872
}
873
874
+ #ws_icon_selector {
875
+ $tabTopPadding: 4px;
876
+ $tabHorizontalPadding: 4px;
877
+
878
+ .ws_tool_tab_nav {
879
+ display: inline-block;
880
+ margin-top: 2px;
881
+
882
+ li {
883
+ padding: 4px 10px 11px;
884
+ }
885
+
886
+ position: relative;
887
+ }
888
+
889
+ .ws_tool_tab {
890
+ padding: $tabTopPadding $tabHorizontalPadding 2px;
891
+ max-height: 324px;
892
+ overflow-y: auto;
893
+ }
894
+ }
895
+
896
/* MP6 admin style compatibility */
897
#ws_icon_selector .ws_icon_option .icon16::before {
898
margin: 0;
907
margin: 2px;
908
}
909
910
/************************************
911
Embedded page selector
912
*************************************/
1353
filter: alpha(opacity=60);
1354
opacity: 0.6;
1355
-moz-opacity:0.6;
1356
}
1357
1358
#ws_role_access_overlay_content {
1697
display: none;
1698
}
1699
1700
+ //You can't deselect the current user. It always stays in the visible actor list.
1701
+ tr.ws_user_must_be_selected {
1702
.ws_user_action_button {
1703
display: none;
1704
}
1757
Tooltips and hints
1758
*************************************/
1759
1760
+ .ws_tooltip_trigger, .ws_field_tooltip_trigger {
1761
cursor: pointer;
1762
}
1763
1774
max-width: 300px;
1775
}
1776
1777
+ .ws_field_tooltip_trigger .dashicons {
1778
+ font-size: 16px;
1779
+ height: 16px;
1780
+ vertical-align: bottom;
1781
+ }
1782
+
1783
+ .ws_field_tooltip_trigger {
1784
+ color: #a1a1a1;
1785
+ }
1786
+
1787
//Tooltips on the settings page.
1788
#ws_plugin_settings_form .ws_tooltip_trigger .dashicons {
1789
font-size: 18px;
1836
margin-left: 0.5em;
1837
}
1838
1839
+ .ws_ame_doc_box {
1840
+ .hndle {
1841
+ cursor: default !important;
1842
+ }
1843
+
1844
+ ul {
1845
+ list-style: disc outside;
1846
+ margin-left: 1em;
1847
+ }
1848
+
1849
+ li > ul {
1850
+ margin-top: 6px;
1851
+ }
1852
+
1853
+ .button-link .toggle-indicator::before {
1854
+ margin-top: 4px;
1855
+ width: 20px;
1856
+ -webkit-border-radius: 50%;
1857
+ border-radius: 50%;
1858
+ text-indent: -1px;
1859
+
1860
+ content: "\f142";
1861
+ display: inline-block;
1862
+ font: normal 20px/1 dashicons;
1863
+
1864
+ -webkit-font-smoothing: antialiased;
1865
+ -moz-osx-font-smoothing: grayscale;
1866
+ text-decoration: none !important;
1867
+ }
1868
+
1869
+ &.closed .button-link .toggle-indicator::before {
1870
+ content: "\f140";
1871
+ }
1872
+ }
1873
1874
/************************************
1875
Copy Permissions dialog
includes/editor-page.php CHANGED
@@ -9,13 +9,6 @@ $is_second_toolbar_visible = isset($_COOKIE['ame-show-second-toolbar']) && (intv
9
$is_compact_layout_enabled = isset($_COOKIE['ame-compact-layout']) && (intval($_COOKIE['ame-compact-layout']) === 1);
10
$is_multisite = is_multisite();
11
12
- $wrap_classes = array('wrap');
13
- if ( $is_pro_version ) {
14
- $wrap_classes[] = 'ame-is-pro-version';
15
- } else {
16
- $wrap_classes[] = 'ame-is-free-version';
17
- }
18
-
19
$icons = array(
20
'cut' => '/gnome-icon-theme/edit-cut-blue.png',
21
'copy' => '/gion/edit-copy.png',
@@ -68,7 +61,7 @@ if ( !empty($_GET['message']) ){
68
69
include dirname(__FILE__) . '/../modules/access-editor/access-editor-template.php';
70
$extrasDirectory = dirname(__FILE__) . '/../extras';
71
- if ( apply_filters('admin_menu_editor_is_pro', false) ) {
72
include $extrasDirectory . '/menu-color-dialog.php';
73
include $extrasDirectory . '/copy-permissions-dialog.php';
74
}
@@ -342,22 +335,37 @@ function ame_output_sort_buttons($icons) {
342
?>
343
344
<!-- Menu icon selector widget -->
345
- <?php $iconSelectorClass = $editor_data['show_extra_icons'] ? 'ws_with_more_icons' : ''; ?>
346
- <div id="ws_icon_selector" class="<?php echo $iconSelectorClass; ?>" style="display: none;">
347
<?php
348
//Let the user select a custom icon via the media uploader.
349
//We only support the new WP 3.5+ media API. Hence the function_exists() check.
350
if ( function_exists('wp_enqueue_media') ):
351
- ?>
352
<input type="button" class="button"
353
- id="ws_choose_icon_from_media"
354
- title="Upload an image or choose one from your media library"
355
- value="Media Library">
356
<div class="clear"></div>
357
- <?php
358
endif;
359
?>
360
361
<?php
362
//The old "menu-icon-something" icons are only available in WP 3.8.x and below. Newer versions use Dashicons.
363
//Plugins can change $wp_version to something useless for security, so lets check if Dashicons are available
@@ -462,16 +470,13 @@ function ame_output_sort_buttons($icons) {
462
<img src="<?php echo esc_attr(admin_url('images/loading.gif')); ?>">
463
</div>
464
465
466
- <?php if ($editor_data['dashicons_available']): ?>
467
- <!-- Only show this button on recent WP versions where Dashicons are included. -->
468
- <input type="button" class="button"
469
- id="ws_show_more_icons"
470
- title="Toggle additional icons"
471
- value="<?php echo esc_attr($editor_data['show_extra_icons'] ? 'Less &#x25B2;' : 'More &#x25BC;'); ?>">
472
- <?php endif; ?>
473
474
- <div class="clear"></div>
475
</div>
476
477
<span id="ws-ame-screen-meta-contents" style="display:none;">
@@ -482,14 +487,6 @@ function ame_output_sort_buttons($icons) {
482
}
483
?> /> Hide advanced options
484
</label><br>
485
-
486
- <label for="ws-show-extra-icons">
487
- <input type="checkbox" id="ws-show-extra-icons"<?php
488
- if ( $this->options['show_extra_icons'] ){
489
- echo ' checked="checked"';
490
- }
491
- ?> /> Show extra menu icons
492
- </label>
493
</span>
494
495
@@ -555,12 +552,13 @@ function ame_output_sort_buttons($icons) {
555
</div>
556
557
<?php
558
- if ( apply_filters('admin_menu_editor_is_pro', false) ) {
559
include $extrasDirectory . '/page-dropdown.php';
560
}
561
?>
562
563
564
<script type='text/javascript'>
565
var defaultMenu = <?php echo $editor_data['default_menu_js']; ?>;
566
var customMenu = <?php echo $editor_data['custom_menu_js']; ?>;
9
$is_compact_layout_enabled = isset($_COOKIE['ame-compact-layout']) && (intval($_COOKIE['ame-compact-layout']) === 1);
10
$is_multisite = is_multisite();
11
12
$icons = array(
13
'cut' => '/gnome-icon-theme/edit-cut-blue.png',
14
'copy' => '/gion/edit-copy.png',
61
62
include dirname(__FILE__) . '/../modules/access-editor/access-editor-template.php';
63
$extrasDirectory = dirname(__FILE__) . '/../extras';
64
+ if ( $is_pro_version ) {
65
include $extrasDirectory . '/menu-color-dialog.php';
66
include $extrasDirectory . '/copy-permissions-dialog.php';
67
}
335
?>
336
337
<!-- Menu icon selector widget -->
338
+ <div id="ws_icon_selector" class="ws_with_more_icons" style="display: none;">
339
+
340
+ <div id="ws_icon_source_tabs">
341
+ <ul class="ws_tool_tab_nav">
342
+ <?php
343
+ $iconSelectorTabs = apply_filters(
344
+ 'admin_menu_editor-icon_selector_tabs',
345
+ array('ws_core_icons_tab' => 'Dashicons')
346
+ );
347
+ foreach($iconSelectorTabs as $id => $caption) {
348
+ printf('<li><a href="#%s">%s</a></li>', esc_attr($id), $caption);
349
+ }
350
+ ?>
351
+ </ul>
352
+
353
<?php
354
//Let the user select a custom icon via the media uploader.
355
//We only support the new WP 3.5+ media API. Hence the function_exists() check.
356
if ( function_exists('wp_enqueue_media') ):
357
+ ?>
358
<input type="button" class="button"
359
+ id="ws_choose_icon_from_media"
360
+ title="Upload an image or choose one from your media library"
361
+ value="Media Library">
362
<div class="clear"></div>
363
+ <?php
364
endif;
365
?>
366
367
+ <div class="ws_tool_tab" id="ws_core_icons_tab">
368
+
369
<?php
370
//The old "menu-icon-something" icons are only available in WP 3.8.x and below. Newer versions use Dashicons.
371
//Plugins can change $wp_version to something useless for security, so lets check if Dashicons are available
470
<img src="<?php echo esc_attr(admin_url('images/loading.gif')); ?>">
471
</div>
472
473
+ <div class="clear"></div>
474
+ </div>
475
476
+ <?php do_action('admin_menu_editor-icon_selector'); ?>
477
+
478
+ </div><!-- tab container -->
479
480
</div>
481
482
<span id="ws-ame-screen-meta-contents" style="display:none;">
487
}
488
?> /> Hide advanced options
489
</label><br>
490
</span>
491
492
552
</div>
553
554
<?php
555
+ if ( $is_pro_version ) {
556
include $extrasDirectory . '/page-dropdown.php';
557
}
558
?>
559
560
561
+ <!--suppress JSUnusedLocalSymbols These variables are actually used by menu-editor.js -->
562
<script type='text/javascript'>
563
var defaultMenu = <?php echo $editor_data['default_menu_js']; ?>;
564
var customMenu = <?php echo $editor_data['custom_menu_js']; ?>;
includes/menu-editor-core.php CHANGED
@@ -20,6 +20,11 @@ require $thisDirectory . '/module.php';
20
21
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
22
const WPML_CONTEXT = 'admin-menu-editor menu texts';
23
/**
24
* @var string The heading tag to use for admin pages.
25
*/
@@ -149,6 +154,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
149
//This usually applies to new items added by other plugins and, in Multisite, items that exist on
150
//the current site but did not exist on the site where the user last edited the menu configuration.
151
'unused_item_position' => 'relative', //"relative" or "bottom".
152
);
153
$this->serialize_with_json = false; //(Don't) store the options in JSON format
154
@@ -447,7 +455,20 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
447
if ( !$this->user_can_access_current_page() ) {
448
$this->log_security_note('DENY access.');
449
$message = 'You do not have sufficient permissions to access this admin page.';
450
- if ( $this->options['security_logging_enabled'] ) {
451
$message .= '<p><strong>Admin Menu Editor security log</strong></p>';
452
$message .= $this->get_formatted_security_log();
453
}
@@ -642,6 +663,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
642
643
//Lodash library
644
wp_register_auto_versioned_script('ame-lodash', plugins_url('js/lodash.min.js', $this->plugin_file));
645
646
//Move admin notices (e.g. "Settings saved") below editor tabs.
647
//This is a separate script because it has to run after common.js which is loaded in the page footer.
@@ -715,14 +738,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
715
foreach($logins_to_include as $login) {
716
$user = get_user_by('login', $login);
717
if ( !empty($user) ) {
718
- $users[$login] = array(
719
- 'user_login' => $user->get('user_login'),
720
- 'id' => $user->ID,
721
- 'roles' => !empty($user->roles) ? (array)($user->roles) : array(),
722
- 'capabilities' => $this->castValuesToBool($user->caps),
723
- 'display_name' => $user->display_name,
724
- 'is_super_admin' => is_multisite() && is_super_admin($user->ID),
725
- );
726
}
727
}
728
@@ -763,17 +779,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
763
//Remove the default jQuery Form plugin to prevent conflicts with our custom version.
764
wp_dequeue_script('jquery-form');
765
766
- $showExtraIcons = (boolean)$this->options['show_extra_icons'];
767
- if ( isset($_COOKIE['ame-show-extra-icons']) && is_numeric($_COOKIE['ame-show-extra-icons']) ) {
768
- $showExtraIcons = intval($_COOKIE['ame-show-extra-icons']) > 0;
769
- }
770
-
771
//The editor will need access to some of the plugin data and WP data.
772
$script_data = array(
773
'imagesUrl' => plugins_url('images', $this->plugin_file),
774
'adminAjaxUrl' => admin_url('admin-ajax.php'),
775
'hideAdvancedSettings' => (boolean)$this->options['hide_advanced_settings'],
776
- 'showExtraIcons' => $showExtraIcons,
777
'submenuIconsEnabled' => $this->options['submenu_icons_enabled'],
778
779
'hideAdvancedSettingsNonce' => wp_create_nonce('ws_ame_save_screen_options'),
@@ -817,6 +828,24 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
817
wp_localize_script('menu-editor', 'wsEditorData', $script_data);
818
}
819
820
/**
821
* Move editor scripts closer to the top of the script queue.
822
*
@@ -1588,8 +1617,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1588
$item = ameMenuItem::apply_defaults($item);
1589
$item = ameMenuItem::apply_filters($item, $item_type, $parent_file); //may cause side-effects
1590
1591
$item = $this->set_final_menu_capability($item, $parent);
1592
- if ( !$this->options['security_logging_enabled'] ) {
1593
unset($item['access_check_log']); //Throw away the log to conserve memory.
1594
}
1595
$this->add_access_lookup($item, $item_type);
@@ -1668,6 +1703,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1668
str_repeat('=', 79),
1669
'Figuring out what capability the user will need to access this item...'
1670
);
1671
1672
//The user can configure the plugin to automatically hide all submenu items if the parent menu is hidden.
1673
//This is the opposite of how WordPress usually handles submenu permissions, so it's optional.
@@ -1699,6 +1735,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1699
//Check if the current user can access this menu.
1700
$user_has_access = true;
1701
$cap_to_use = '';
1702
if ( !empty($item['access_level']) ) {
1703
$cap_to_use = $item['access_level'];
1704
@@ -1720,6 +1760,16 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1720
htmlentities($cap_to_use),
1721
$user_has_cap ? 'HAS' : 'DOES NOT have'
1722
);
1723
}
1724
1725
$user_has_access = $user_has_access && $user_has_cap;
@@ -1729,6 +1779,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1729
}
1730
1731
if ( !empty($item['extra_capability']) ) {
1732
$user_has_cap = $this->current_user_can($item['extra_capability']);
1733
$user_has_access = $user_has_access && $user_has_cap;
1734
$cap_to_use = $item['extra_capability'];
@@ -1738,10 +1790,35 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1738
htmlentities($cap_to_use),
1739
$user_has_cap ? 'HAS' : 'DOES NOT have'
1740
);
1741
} else {
1742
$item['access_check_log'][] = 'No "extra capability" set.';
1743
}
1744
1745
$capability = $user_has_access ? $cap_to_use : 'do_not_allow';
1746
$item['access_check_log'][] = 'Final capability setting: ' . $capability;
1747
$item['access_check_log'][] = str_repeat('=', 79);
@@ -1935,6 +2012,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1935
}
1936
}
1937
1938
1939
$this->save_options();
1940
wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
@@ -2008,7 +2093,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2008
* This includes the page heading and tab list.
2009
*/
2010
public function display_settings_page_header() {
2011
- echo '<div class="wrap">';
2012
printf(
2013
'<%1$s id="ws_ame_editor_heading">%2$s</%1$s>',
2014
self::$admin_heading_tag,
@@ -2669,7 +2761,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2669
return substr($string, -$len) === $suffix;
2670
}
2671
2672
- private function castValuesToBool($capabilities) {
2673
if ( !is_array($capabilities) ) {
2674
if ( empty($capabilities) ) {
2675
$capabilities = array();
@@ -2802,7 +2894,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2802
* Get one of the plugin configuration values.
2803
*
2804
* @param string $name Option name.
2805
- * @return mixed
2806
*/
2807
public function get_plugin_option($name) {
2808
if ( array_key_exists($name, $this->options) ) {
@@ -2828,7 +2920,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2828
* @param string|array $message The message to add tot he log, or an array of messages.
2829
*/
2830
private function log_security_note($message) {
2831
- if ( !$this->options['security_logging_enabled'] ) {
2832
return;
2833
}
2834
if ( is_array($message) ) {
@@ -2838,6 +2930,13 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2838
}
2839
}
2840
2841
/**
2842
* Callback for "admin_notices".
2843
*/
20
21
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
22
const WPML_CONTEXT = 'admin-menu-editor menu texts';
23
+
24
+ const VERBOSITY_LOW = 1;
25
+ const VERBOSITY_NORMAL = 2;
26
+ const VERBOSITY_VERBOSE = 5;
27
+
28
/**
29
* @var string The heading tag to use for admin pages.
30
*/
154
//This usually applies to new items added by other plugins and, in Multisite, items that exist on
155
//the current site but did not exist on the site where the user last edited the menu configuration.
156
'unused_item_position' => 'relative', //"relative" or "bottom".
157
+
158
+ //Verbosity level of menu permission errors.
159
+ 'error_verbosity' => self::VERBOSITY_NORMAL,
160
);
161
$this->serialize_with_json = false; //(Don't) store the options in JSON format
162
455
if ( !$this->user_can_access_current_page() ) {
456
$this->log_security_note('DENY access.');
457
$message = 'You do not have sufficient permissions to access this admin page.';
458
+
459
+ if ( ($this->options['error_verbosity'] >= self::VERBOSITY_NORMAL) ) {
460
+ $current_item = $this->get_current_menu_item();
461
+ if ( isset($current_item, $current_item['access_decision_reason']) ) {
462
+ $message .= sprintf(
463
+ '<p>Reason: %s</p>',
464
+ htmlentities($current_item['access_decision_reason'])
465
+ );
466
+ }
467
+ }
468
+
469
+ if ($this->options['security_logging_enabled']
470
+ || ($this->options['error_verbosity'] >= self::VERBOSITY_VERBOSE)
471
+ ) {
472
$message .= '<p><strong>Admin Menu Editor security log</strong></p>';
473
$message .= $this->get_formatted_security_log();
474
}
663
664
//Lodash library
665
wp_register_auto_versioned_script('ame-lodash', plugins_url('js/lodash.min.js', $this->plugin_file));
666
+ //Knockout
667
+ wp_register_auto_versioned_script('knockout', plugins_url('js/knockout.js', $this->plugin_file));
668
669
//Move admin notices (e.g. "Settings saved") below editor tabs.
670
//This is a separate script because it has to run after common.js which is loaded in the page footer.
738
foreach($logins_to_include as $login) {
739
$user = get_user_by('login', $login);
740
if ( !empty($user) ) {
741
+ $users[$login] = $this->user_to_property_map($user);
742
}
743
}
744
779
//Remove the default jQuery Form plugin to prevent conflicts with our custom version.
780
wp_dequeue_script('jquery-form');
781
782
//The editor will need access to some of the plugin data and WP data.
783
$script_data = array(
784
'imagesUrl' => plugins_url('images', $this->plugin_file),
785
'adminAjaxUrl' => admin_url('admin-ajax.php'),
786
'hideAdvancedSettings' => (boolean)$this->options['hide_advanced_settings'],
787
+ 'showExtraIcons' => true, //No longer used.
788
'submenuIconsEnabled' => $this->options['submenu_icons_enabled'],
789
790
'hideAdvancedSettingsNonce' => wp_create_nonce('ws_ame_save_screen_options'),
828
wp_localize_script('menu-editor', 'wsEditorData', $script_data);
829
}
830
831
+ /**
832
+ * Convert a WP_User instance to an associative array with the keys defined
833
+ * in the AmeUserPropertyMap interface in actor-manager.ts.
834
+ *
835
+ * @param WP_User $user
836
+ * @return array
837
+ */
838
+ public function user_to_property_map($user) {
839
+ return array(
840
+ 'user_login' => $user->get('user_login'),
841
+ 'id' => $user->ID,
842
+ 'roles' => !empty($user->roles) ? (array)($user->roles) : array(),
843
+ 'capabilities' => $this->castValuesToBool($user->caps),
844
+ 'display_name' => $user->display_name,
845
+ 'is_super_admin' => is_multisite() && is_super_admin($user->ID),
846
+ );
847
+ }
848
+
849
/**
850
* Move editor scripts closer to the top of the script queue.
851
*
1617
$item = ameMenuItem::apply_defaults($item);
1618
$item = ameMenuItem::apply_filters($item, $item_type, $parent_file); //may cause side-effects
1619
1620
+ //Store the hierarchical menu title for errors and debugging messages.
1621
+ $item['full_title'] = $item['menu_title'];
1622
+ if ( isset($parent, $parent['menu_title']) ) {
1623
+ $item['full_title'] = $parent['menu_title'] . ' → ' . $item['full_title'];
1624
+ }
1625
+
1626
$item = $this->set_final_menu_capability($item, $parent);
1627
+ if ( !$this->should_store_security_log() ) {
1628
unset($item['access_check_log']); //Throw away the log to conserve memory.
1629
}
1630
$this->add_access_lookup($item, $item_type);
1703
str_repeat('=', 79),
1704
'Figuring out what capability the user will need to access this item...'
1705
);
1706
+ $debug_title = ameMenuItem::get($item, 'full_title', ameMenuItem::get($item, 'menu_title', '[untitled menu]'));
1707
1708
//The user can configure the plugin to automatically hide all submenu items if the parent menu is hidden.
1709
//This is the opposite of how WordPress usually handles submenu permissions, so it's optional.
1735
//Check if the current user can access this menu.
1736
$user_has_access = true;
1737
$cap_to_use = '';
1738
+
1739
+ $user_has_default_cap = null;
1740
+ $reason = isset($item['access_decision_reason']) ? $item['access_decision_reason'] : null;
1741
+
1742
if ( !empty($item['access_level']) ) {
1743
$cap_to_use = $item['access_level'];
1744
1760
htmlentities($cap_to_use),
1761
$user_has_cap ? 'HAS' : 'DOES NOT have'
1762
);
1763
+
1764
+ $user_has_default_cap = $user_has_cap;
1765
+ if ( is_null($reason) ) {
1766
+ $reason = sprintf(
1767
+ 'The current user %1$s the "%2$s" capability that is required to access the "%3$s" menu item.',
1768
+ $user_has_cap ? 'has' : 'doesn\'t have',
1769
+ $cap_to_use,
1770
+ $debug_title
1771
+ );
1772
+ }
1773
}
1774
1775
$user_has_access = $user_has_access && $user_has_cap;
1779
}
1780
1781
if ( !empty($item['extra_capability']) ) {
1782
+ $had_access_before_extra_cap = $user_has_access;
1783
+
1784
$user_has_cap = $this->current_user_can($item['extra_capability']);
1785
$user_has_access = $user_has_access && $user_has_cap;
1786
$cap_to_use = $item['extra_capability'];
1790
htmlentities($cap_to_use),
1791
$user_has_cap ? 'HAS' : 'DOES NOT have'
1792
);
1793
+
1794
+ //Provide a more detailed reason for situations where the extra cap disagrees.
1795
+ if ( !$user_has_access ) {
1796
+ if ( $had_access_before_extra_cap && !$user_has_cap ) {
1797
+ $reason = sprintf(
1798
+ 'The current user doesn\'t have the extra capability "%1$s" that is required to access the "%2$s" menu item.',
1799
+ $item['extra_capability'],
1800
+ $debug_title
1801
+ );
1802
+ } else if ( $user_has_cap && !$user_has_default_cap && !is_null($user_has_default_cap) ) {
1803
+ //Note: Will this ever show up? If the user doesn't have the required cap,
1804
+ //WordPress won't even register the menu. AME won't be able to identify the menu for that user.
1805
+ $reason = sprintf(
1806
+ 'The current user has the extra capability "%1$s". However, they don\'t ' .
1807
+ 'have the "%2$s" capability that is also required to access "%3$s".',
1808
+ $item['extra_capability'],
1809
+ $item['access_level'],
1810
+ $debug_title
1811
+ );
1812
+ }
1813
+ }
1814
} else {
1815
$item['access_check_log'][] = 'No "extra capability" set.';
1816
}
1817
1818
+ if ( !is_null($reason) ) {
1819
+ $item['access_decision_reason'] = $reason;
1820
+ }
1821
+
1822
$capability = $user_has_access ? $cap_to_use : 'do_not_allow';
1823
$item['access_check_log'][] = 'Final capability setting: ' . $capability;
1824
$item['access_check_log'][] = str_repeat('=', 79);
2012
}
2013
}
2014
2015
+ //How verbose "access denied" errors should be.
2016
+ if ( !empty($this->post['error_verbosity']) ) {
2017
+ $error_verbosity = intval($this->post['error_verbosity']);
2018
+ $valid_verbosity_levels = array(self::VERBOSITY_LOW, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE);
2019
+ if ( in_array($error_verbosity, $valid_verbosity_levels) ) {
2020
+ $this->options['error_verbosity'] = $error_verbosity;
2021
+ }
2022
+ }
2023
2024
$this->save_options();
2025
wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
2093
* This includes the page heading and tab list.
2094
*/
2095
public function display_settings_page_header() {
2096
+ $wrap_classes = array('wrap');
2097
+ if ( $this->is_pro_version() ) {
2098
+ $wrap_classes[] = 'ame-is-pro-version';
2099
+ } else {
2100
+ $wrap_classes[] = 'ame-is-free-version';
2101
+ }
2102
+
2103
+ echo '<div class="', implode(' ', $wrap_classes), '">';
2104
printf(
2105
'<%1$s id="ws_ame_editor_heading">%2$s</%1$s>',
2106
self::$admin_heading_tag,
2761
return substr($string, -$len) === $suffix;
2762
}
2763
2764
+ public function castValuesToBool($capabilities) {
2765
if ( !is_array($capabilities) ) {
2766
if ( empty($capabilities) ) {
2767
$capabilities = array();
2894
* Get one of the plugin configuration values.
2895
*
2896
* @param string $name Option name.
2897
+ * @return mixed|null
2898
*/
2899
public function get_plugin_option($name) {
2900
if ( array_key_exists($name, $this->options) ) {
2920
* @param string|array $message The message to add tot he log, or an array of messages.
2921
*/
2922
private function log_security_note($message) {
2923
+ if ( !$this->should_store_security_log() ) {
2924
return;
2925
}
2926
if ( is_array($message) ) {
2930
}
2931
}
2932
2933
+ private function should_store_security_log() {
2934
+ return (
2935
+ $this->options['security_logging_enabled']
2936
+ || ($this->options['error_verbosity'] >= self::VERBOSITY_VERBOSE)
2937
+ );
2938
+ }
2939
+
2940
/**
2941
* Callback for "admin_notices".
2942
*/
includes/module.php CHANGED
@@ -21,10 +21,15 @@ abstract class ameModule {
21
$this->moduleId = basename($this->moduleDir);
22
}
23
24
//Register the module tab.
25
if ( ($this->tabSlug !== '') && is_string($this->tabSlug) ) {
26
add_action('admin_menu_editor-tabs', array($this, 'addTab'), $this->tabOrder);
27
add_action('admin_menu_editor-section-' . $this->tabSlug, array($this, 'displaySettingsPage'));
28
}
29
}
30
@@ -61,10 +66,25 @@ abstract class ameModule {
61
protected function outputTemplate($name) {
62
$templateFile = $this->moduleDir . '/' . $name . '-template.php';
63
if ( file_exists($templateFile) ) {
64
/** @noinspection PhpIncludeInspection */
65
require $templateFile;
66
return true;
67
}
68
return false;
69
}
70
}
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.
27
if ( ($this->tabSlug !== '') && is_string($this->tabSlug) ) {
28
add_action('admin_menu_editor-tabs', array($this, 'addTab'), $this->tabOrder);
29
add_action('admin_menu_editor-section-' . $this->tabSlug, array($this, 'displaySettingsPage'));
30
+
31
+ add_action('admin_menu_editor-enqueue_scripts-' . $this->tabSlug, array($this, 'enqueueTabScripts'));
32
+ add_action('admin_menu_editor-enqueue_styles-' . $this->tabSlug, array($this, 'enqueueTabStyles'));
33
}
34
}
35
66
protected function outputTemplate($name) {
67
$templateFile = $this->moduleDir . '/' . $name . '-template.php';
68
if ( file_exists($templateFile) ) {
69
+ /** @noinspection PhpUnusedLocalVariableInspection Used in some templates. */
70
+ $moduleTabUrl = $this->getTabUrl();
71
+
72
/** @noinspection PhpIncludeInspection */
73
require $templateFile;
74
return true;
75
}
76
return false;
77
}
78
+
79
+ public function registerScripts() {
80
+ //Override this method to register scripts.
81
+ }
82
+
83
+ public function enqueueTabScripts() {
84
+ //Override this method to add scripts to the $this->tabSlug tab.
85
+ }
86
+
87
+ public function enqueueTabStyles() {
88
+ //Override this method to add stylesheets to the $this->tabSlug tab.
89
+ }
90
}
includes/settings-page.php CHANGED
@@ -250,6 +250,51 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
250
</td>
251
</tr>
252
253
<tr>
254
<th scope="row">Debugging</th>
255
<td>
250
</td>
251
</tr>
252
253
+ <tr>
254
+ <th scope="row">Error verbosity level</th>
255
+ <td>
256
+ <fieldset id="ame-submenu-icons-settings">
257
+ <p>
258
+ <label>
259
+ <input type="radio" name="error_verbosity" value="<?php echo WPMenuEditor::VERBOSITY_LOW ?>>"
260
+ <?php checked(WPMenuEditor::VERBOSITY_LOW, $settings['error_verbosity']); ?>>
261
+ Low
262
+
263
+ <br><span class="description">
264
+ Shows a generic error message without any details.
265
+ </span>
266
+ </label>
267
+ </p>
268
+
269
+ <p>
270
+ <label>
271
+ <input type="radio" name="error_verbosity" value="<?php echo WPMenuEditor::VERBOSITY_NORMAL; ?>>"
272
+ <?php checked(WPMenuEditor::VERBOSITY_NORMAL, $settings['error_verbosity']); ?>>
273
+ Normal
274
+
275
+ <br><span class="description">
276
+ Shows a one or two sentence explanation. For example: "The current user doesn't have
277
+ the "manage_options" capability that is required to access the "Settings" menu item."
278
+ </span>
279
+ </label>
280
+ </p>
281
+
282
+ <p>
283
+ <label>
284
+ <input type="radio" name="error_verbosity" value="<?php echo WPMenuEditor::VERBOSITY_VERBOSE; ?>>"
285
+ <?php checked(WPMenuEditor::VERBOSITY_VERBOSE, $settings['error_verbosity']); ?>>
286
+ Verbose
287
+
288
+ <br><span class="description">
289
+ Like "normal", but also includes a log of menu settings and permissions that
290
+ caused the current menu to be hidden. Useful for debugging.
291
+ </span>
292
+ </label>
293
+ </p>
294
+ </fieldset>
295
+ </td>
296
+ </tr>
297
+
298
<tr>
299
<th scope="row">Debugging</th>
300
<td>
js/actor-manager.js CHANGED
@@ -68,14 +68,17 @@ var AmeRole = (function (_super) {
68
}(AmeBaseActor));
69
var AmeUser = (function (_super) {
70
__extends(AmeUser, _super);
71
- function AmeUser(userLogin, displayName, capabilities, roles, isSuperAdmin) {
72
if (isSuperAdmin === void 0) { isSuperAdmin = false; }
73
_super.call(this, 'user:' + userLogin, displayName, capabilities);
74
this.isSuperAdmin = false;
75
this.actorTypeSpecificity = 10;
76
this.userLogin = userLogin;
77
this.roles = roles;
78
this.isSuperAdmin = isSuperAdmin;
79
if (this.isSuperAdmin) {
80
this.groupActors.push(AmeSuperAdmin.permanentActorId);
81
}
@@ -83,6 +86,13 @@ var AmeUser = (function (_super) {
83
this.groupActors.push('role:' + this.roles[i]);
84
}
85
}
86
return AmeUser;
87
}(AmeBaseActor));
88
var AmeSuperAdmin = (function (_super) {
@@ -112,7 +122,7 @@ var AmeActorManager = (function () {
112
_this.roles[role.name] = role;
113
});
114
AmeActorManager._.forEach(users, function (userDetails) {
115
- var user = new AmeUser(userDetails.user_login, userDetails.display_name, userDetails.capabilities, userDetails.roles, userDetails.is_super_admin);
116
_this.users[user.userLogin] = user;
117
});
118
if (this.isMultisite) {
68
}(AmeBaseActor));
69
var AmeUser = (function (_super) {
70
__extends(AmeUser, _super);
71
+ function AmeUser(userLogin, displayName, capabilities, roles, isSuperAdmin, userId) {
72
if (isSuperAdmin === void 0) { isSuperAdmin = false; }
73
_super.call(this, 'user:' + userLogin, displayName, capabilities);
74
+ this.userId = 0;
75
this.isSuperAdmin = false;
76
+ this.avatarHTML = '';
77
this.actorTypeSpecificity = 10;
78
this.userLogin = userLogin;
79
this.roles = roles;
80
this.isSuperAdmin = isSuperAdmin;
81
+ this.userId = userId || 0;
82
if (this.isSuperAdmin) {
83
this.groupActors.push(AmeSuperAdmin.permanentActorId);
84
}
86
this.groupActors.push('role:' + this.roles[i]);
87
}
88
}
89
+ AmeUser.createFromProperties = function (properties) {
90
+ var user = new AmeUser(properties.user_login, properties.display_name, properties.capabilities, properties.roles, properties.is_super_admin, properties.hasOwnProperty('id') ? properties.id : null);
91
+ if (properties.avatar_html) {
92
+ user.avatarHTML = properties.avatar_html;
93
+ }
94
+ return user;
95
+ };
96
return AmeUser;
97
}(AmeBaseActor));
98
var AmeSuperAdmin = (function (_super) {
122
_this.roles[role.name] = role;
123
});
124
AmeActorManager._.forEach(users, function (userDetails) {
125
+ var user = AmeUser.createFromProperties(userDetails);
126
_this.users[user.userLogin] = user;
127
});
128
if (this.isMultisite) {
js/actor-manager.ts CHANGED
@@ -81,11 +81,23 @@ class AmeRole extends AmeBaseActor {
81
}
82
}
83
84
class AmeUser extends AmeBaseActor {
85
userLogin: string;
86
roles: string[];
87
isSuperAdmin: boolean = false;
88
groupActors: string[];
89
90
protected actorTypeSpecificity = 10;
91
@@ -94,13 +106,15 @@ class AmeUser extends AmeBaseActor {
94
displayName: string,
95
capabilities: CapabilityMap,
96
roles: string[],
97
- isSuperAdmin: boolean = false
98
) {
99
super('user:' + userLogin, displayName, capabilities);
100
101
this.userLogin = userLogin;
102
this.roles = roles;
103
this.isSuperAdmin = isSuperAdmin;
104
105
if (this.isSuperAdmin) {
106
this.groupActors.push(AmeSuperAdmin.permanentActorId);
@@ -109,6 +123,23 @@ class AmeUser extends AmeBaseActor {
109
this.groupActors.push('role:' + this.roles[i]);
110
}
111
}
112
}
113
114
class AmeSuperAdmin extends AmeBaseActor {
@@ -149,14 +180,8 @@ class AmeActorManager {
149
this.roles[role.name] = role;
150
});
151
152
- AmeActorManager._.forEach(users, (userDetails) => {
153
- var user = new AmeUser(
154
- userDetails.user_login,
155
- userDetails.display_name,
156
- userDetails.capabilities,
157
- userDetails.roles,
158
- userDetails.is_super_admin
159
- );
160
this.users[user.userLogin] = user;
161
});
162
81
}
82
}
83
84
+ interface AmeUserPropertyMap {
85
+ user_login: string;
86
+ display_name: string;
87
+ capabilities: CapabilityMap;
88
+ roles : string[];
89
+ is_super_admin: boolean;
90
+ id?: number;
91
+ avatar_html?: string;
92
+ }
93
+
94
class AmeUser extends AmeBaseActor {
95
userLogin: string;
96
+ userId: number = 0;
97
roles: string[];
98
isSuperAdmin: boolean = false;
99
groupActors: string[];
100
+ avatarHTML: string = '';
101
102
protected actorTypeSpecificity = 10;
103
106
displayName: string,
107
capabilities: CapabilityMap,
108
roles: string[],
109
+ isSuperAdmin: boolean = false,
110
+ userId?: number
111
) {
112
super('user:' + userLogin, displayName, capabilities);
113
114
this.userLogin = userLogin;
115
this.roles = roles;
116
this.isSuperAdmin = isSuperAdmin;
117
+ this.userId = userId || 0;
118
119
if (this.isSuperAdmin) {
120
this.groupActors.push(AmeSuperAdmin.permanentActorId);
123
this.groupActors.push('role:' + this.roles[i]);
124
}
125
}
126
+
127
+ static createFromProperties(properties: AmeUserPropertyMap): AmeUser {
128
+ let user = new AmeUser(
129
+ properties.user_login,
130
+ properties.display_name,
131
+ properties.capabilities,
132
+ properties.roles,
133
+ properties.is_super_admin,
134
+ properties.hasOwnProperty('id') ? properties.id : null
135
+ );
136
+
137
+ if (properties.avatar_html) {
138
+ user.avatarHTML = properties.avatar_html;
139
+ }
140
+
141
+ return user;
142
+ }
143
}
144
145
class AmeSuperAdmin extends AmeBaseActor {
180
this.roles[role.name] = role;
181
});
182
183
+ AmeActorManager._.forEach(users, (userDetails: AmeUserPropertyMap) => {
184
+ var user = AmeUser.createFromProperties(userDetails);
185
this.users[user.userLogin] = user;
186
});
187
js/jquery.biscuit.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ interface JQueryStatic {
2
+ //These methods are added by the jquery-cookie plugin.
3
+ cookie: (name: string, value?: string, options?: {}) => string;
4
+ removeCookie: (name: string, options?: {}) => boolean;
5
+ }
js/menu-editor.js CHANGED
@@ -471,7 +471,9 @@ var baseField = {
471
visible: true,
472
473
write: null,
474
- display: null
475
};
476
477
/*
@@ -672,19 +674,49 @@ var knownMenuFields = {
672
}
673
}),
674
675
- 'extra_capability' : $.extend({}, baseField, {
676
caption: 'Required capability',
677
defaultValue: 'read',
678
type: 'text',
679
addDropdown: 'ws_cap_selector',
680
681
display: function(menuItem) {
682
- //Permissions display is a little complicated and could use improvement.
683
var requiredCap = getFieldValue(menuItem, 'access_level', '');
684
var extraCap = getFieldValue(menuItem, 'extra_capability', '');
685
686
var displayValue = extraCap;
687
- if ((extraCap === '') || (extraCap === null)) {
688
displayValue = requiredCap;
689
}
690
@@ -700,13 +732,6 @@ var knownMenuFields = {
700
return;
701
}
702
703
- //It would be redundant to set an extra_capability that it matches access_level.
704
- var requiredCap = getFieldValue(menuItem, 'access_level', '');
705
- var extraCap = getFieldValue(menuItem, 'extra_capability', '');
706
- if (extraCap === '' && value === requiredCap) {
707
- return;
708
- }
709
-
710
menuItem.extra_capability = value;
711
}
712
}),
@@ -797,17 +822,24 @@ var knownMenuFields = {
797
var imageIcon = selectButton.find('img');
798
799
var matches = cssClass.match(/\b(ame-)?menu-icon-([^\s]+)\b/);
800
- var dashiconMatches = iconUrl && iconUrl.match(/^\s*(dashicons-[a-z0-9\-]+)/);
801
802
//Icon URL takes precedence over icon class.
803
- if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' && !dashiconMatches ) {
804
//Regular image icon.
805
cssIcon.hide();
806
imageIcon.prop('src', iconUrl).show();
807
- } else if ( dashiconMatches ) {
808
- //Dashicon.
809
imageIcon.hide();
810
- cssIcon.removeClass().addClass('icon16 dashicons ' + dashiconMatches[1]).show();
811
} else if ( matches ) {
812
//Other CSS-based icon.
813
imageIcon.hide();
@@ -1001,7 +1033,11 @@ function buildEditboxField(entry, field_name, field_settings){
1001
1002
var caption = '';
1003
if (field_settings.standardCaption) {
1004
- caption = '<span class="ws_field_label_text">' + field_settings.caption + '</span><br>';
1005
}
1006
var editField = $('<div>' + caption + '</div>')
1007
.attr('class', className)
@@ -1216,6 +1252,9 @@ function updateItemEditor(containerNode) {
1216
isDefault = (getFieldValue(menuItem, 'extra_capability', '') === '')
1217
&& isEmptyObject(menuItem.grant_access)
1218
&& (!getFieldValue(menuItem, 'restrict_access_to_items', false));
1219
}
1220
1221
field.toggleClass('ws_has_no_default', !hasADefaultValue);
@@ -2372,6 +2411,9 @@ function ameOnDomReady() {
2372
var iconSelector = $('#ws_icon_selector');
2373
var currentIconButton = null; //Keep track of the last clicked icon button.
2374
2375
//When the user clicks one of the available icons, update the menu item.
2376
iconSelector.on('click', '.ws_icon_option', function() {
2377
var selectedIcon = $(this).addClass('ws_selected_icon');
@@ -2425,33 +2467,33 @@ function ameOnDomReady() {
2425
//Highlight the currently selected icon.
2426
iconSelector.find('.ws_selected_icon').removeClass('ws_selected_icon');
2427
2428
- var expandSelector = false;
2429
var classMatches = cssClass.match(/\b(ame-)?menu-icon-([^\s]+)\b/);
2430
- //Dashicons are set via the icon URL field, but they are actually CSS-based.
2431
- var dashiconMatches = iconUrl && iconUrl.match('^\s*(dashicons-[a-z0-9\-]+)\s*#x27;);
2432
2433
- if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' && !dashiconMatches ) {
2434
var currentIcon = iconSelector.find('.ws_icon_option img[src="' + iconUrl + '"]').first().closest('.ws_icon_option');
2435
if ( currentIcon.length > 0 ) {
2436
- currentIcon.addClass('ws_selected_icon').show();
2437
} else {
2438
//Display and highlight the custom image.
2439
customImageOption.find('img').prop('src', iconUrl);
2440
customImageOption.addClass('ws_selected_icon').show().data('icon-url', iconUrl);
2441
}
2442
- } else if ( classMatches || dashiconMatches ) {
2443
- //Highlight the icon that corresponds to the current CSS class or Dashicon name.
2444
- var iconClass = dashiconMatches ? dashiconMatches[1] : ((classMatches[1] ? classMatches[1] : '') + 'icon-' + classMatches[2]);
2445
- var selectedIcon = iconSelector.find('.' + iconClass).closest('.ws_icon_option').addClass('ws_selected_icon');
2446
- //If the icon is one of those hidden by default, automatically expand the selector so it becomes visible.
2447
- if (selectedIcon.hasClass('ws_icon_extra')) {
2448
- expandSelector = true;
2449
- }
2450
}
2451
2452
- expandSelector = expandSelector || (!!wsEditorData.showExtraIcons); //Second argument to toggleClass() must be a boolean, not just truthy/falsy.
2453
- iconSelector.toggleClass('ws_with_more_icons', expandSelector);
2454
- $('#ws_show_more_icons').val(expandSelector ? 'Less \u25B2' : 'More \u25BC');
2455
2456
iconSelector.show();
2457
iconSelector.position({ //Requires jQuery UI.
@@ -2529,16 +2571,6 @@ function ameOnDomReady() {
2529
iconSelector.hide();
2530
});
2531
2532
- //Show/hide additional icons.
2533
- $('#ws_show_more_icons').click(function() {
2534
- iconSelector.toggleClass('ws_with_more_icons');
2535
- wsEditorData.showExtraIcons = iconSelector.hasClass('ws_with_more_icons');
2536
- $(this).val(wsEditorData.showExtraIcons ? 'Less \u25B2' : 'More \u25BC');
2537
-
2538
- //Remember the user's choice.
2539
- $.cookie('ame-show-extra-icons', wsEditorData.showExtraIcons ? '1' : '0', {expires: 90});
2540
- });
2541
-
2542
//Hide the icon selector if the user clicks outside of it.
2543
//Exception: Clicks on "Select icon" buttons are handled above.
2544
$(document).on('mouseup', function(event) {
@@ -4137,6 +4169,10 @@ function ameOnDomReady() {
4137
});
4138
}
4139
4140
4141
//Set up tooltips
4142
$('.ws_tooltip_trigger').qtip({
@@ -4149,6 +4185,49 @@ function ameOnDomReady() {
4149
}
4150
});
4151
4152
//Set up the "additional permissions are available" tooltips.
4153
menuEditorNode.on('mouseenter click', '.ws_ext_permissions_indicator', function() {
4154
var $indicator = $(this);
@@ -4264,15 +4343,11 @@ jQuery(function($){
4264
4265
var screenOptions = $('#ws-ame-screen-meta-contents');
4266
var hideSettingsCheckbox = screenOptions.find('#ws-hide-advanced-settings');
4267
- var extraIconsCheckbox = screenOptions.find('#ws-show-extra-icons');
4268
-
4269
hideSettingsCheckbox.prop('checked', wsEditorData.hideAdvancedSettings);
4270
- extraIconsCheckbox.prop('checked', wsEditorData.showExtraIcons);
4271
4272
//Update editor state when settings change
4273
- $('#ws-hide-advanced-settings, #ws-show-extra-icons').click(function(){
4274
wsEditorData.hideAdvancedSettings = hideSettingsCheckbox.prop('checked');
4275
- wsEditorData.showExtraIcons = extraIconsCheckbox.prop('checked');
4276
4277
//Show/hide advanced settings dynamically as the user changes the setting.
4278
if ($(this).is(hideSettingsCheckbox)) {
@@ -4295,9 +4370,6 @@ jQuery(function($){
4295
'_ajax_nonce' : wsEditorData.hideAdvancedSettingsNonce
4296
}
4297
);
4298
-
4299
- //We also have a cookie for the current user.
4300
- $.cookie('ame-show-extra-icons', wsEditorData.showExtraIcons ? '1' : '0', {expires: 90});
4301
});
4302
4303
//Move our options into the screen meta panel
471
visible: true,
472
473
write: null,
474
+ display: null,
475
+
476
+ tooltip: null
477
};
478
479
/*
674
}
675
}),
676
677
+ 'required_capability_read_only' : $.extend({}, baseField, {
678
caption: 'Required capability',
679
+ defaultValue: 'none',
680
+ type: 'text',
681
+ tooltip: "Only users who have this capability can see the menu. "+
682
+ "The capability can't be changed because it's usually hard-coded in WordPress or the plugin that created the menu."+
683
+ "<br><br>Use the \"Extra capability\" field to restrict access to this menu.",
684
+
685
+ visible: function(menuItem) {
686
+ //Show only in the free version, on non-custom menus.
687
+ return !wsEditorData.wsMenuEditorPro && (menuItem.template_id !== '');
688
+ },
689
+
690
+ display: function(menuItem, displayValue, input) {
691
+ input.prop('readonly', true);
692
+ return getFieldValue(menuItem, 'access_level', '');
693
+ },
694
+
695
+ write: function(menuItem, value) {
696
+ //The required capability is read-only. Ignore writes.
697
+ }
698
+ }),
699
+
700
+ 'extra_capability' : $.extend({}, baseField, {
701
+ caption: 'Extra capability',
702
defaultValue: 'read',
703
type: 'text',
704
addDropdown: 'ws_cap_selector',
705
+ tooltip: function(menuItem) {
706
+ if (menuItem.template_id === '') {
707
+ return 'Only users who have this capability can see the menu.';
708
+ }
709
+ return 'An additional capability check that is applied on top of the required capability.';
710
+ },
711
712
display: function(menuItem) {
713
var requiredCap = getFieldValue(menuItem, 'access_level', '');
714
var extraCap = getFieldValue(menuItem, 'extra_capability', '');
715
716
+ //On custom menus, show the default required cap when no extra cap is selected.
717
+ //Otherwise there would be no visible capability requirements at all.
718
var displayValue = extraCap;
719
+ if ((menuItem.template_id === '') && (extraCap === '')) {
720
displayValue = requiredCap;
721
}
722
732
return;
733
}
734
735
menuItem.extra_capability = value;
736
}
737
}),
822
var imageIcon = selectButton.find('img');
823
824
var matches = cssClass.match(/\b(ame-)?menu-icon-([^\s]+)\b/);
825
+ var iconFontMatches = iconUrl && iconUrl.match(/^\s*((dashicons|ame-fa)-[a-z0-9\-]+)/);
826
827
//Icon URL takes precedence over icon class.
828
+ if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' && !iconFontMatches ) {
829
//Regular image icon.
830
cssIcon.hide();
831
imageIcon.prop('src', iconUrl).show();
832
+ } else if ( iconFontMatches ) {
833
+ cssIcon.removeClass().addClass('icon16');
834
+ if ( iconFontMatches[2] === 'dashicons' ) {
835
+ //Dashicon.
836
+ cssIcon.addClass('dashicons ' + iconFontMatches[1]);
837
+ } else if ( iconFontMatches[2] === 'ame-fa' ) {
838
+ //FontAwesome icon.
839
+ cssIcon.addClass('ame-fa ' + iconFontMatches[1]);
840
+ }
841
imageIcon.hide();
842
+ cssIcon.show();
843
} else if ( matches ) {
844
//Other CSS-based icon.
845
imageIcon.hide();
1033
1034
var caption = '';
1035
if (field_settings.standardCaption) {
1036
+ var tooltip = '';
1037
+ if (field_settings.tooltip !== null) {
1038
+ tooltip = ' <a class="ws_field_tooltip_trigger"><div class="dashicons dashicons-info"></div></a>';
1039
+ }
1040
+ caption = '<span class="ws_field_label_text">' + field_settings.caption + tooltip + '</span><br>';
1041
}
1042
var editField = $('<div>' + caption + '</div>')
1043
.attr('class', className)
1252
isDefault = (getFieldValue(menuItem, 'extra_capability', '') === '')
1253
&& isEmptyObject(menuItem.grant_access)
1254
&& (!getFieldValue(menuItem, 'restrict_access_to_items', false));
1255
+ } else if (fieldName === 'required_capability_read_only') {
1256
+ isDefault = true;
1257
+ hasADefaultValue = true;
1258
}
1259
1260
field.toggleClass('ws_has_no_default', !hasADefaultValue);
2411
var iconSelector = $('#ws_icon_selector');
2412
var currentIconButton = null; //Keep track of the last clicked icon button.
2413
2414
+ var iconSelectorTabs = iconSelector.find('#ws_icon_source_tabs');
2415
+ iconSelectorTabs.tabs();
2416
+
2417
//When the user clicks one of the available icons, update the menu item.
2418
iconSelector.on('click', '.ws_icon_option', function() {
2419
var selectedIcon = $(this).addClass('ws_selected_icon');
2467
//Highlight the currently selected icon.
2468
iconSelector.find('.ws_selected_icon').removeClass('ws_selected_icon');
2469
2470
+ var selectedIcon = null;
2471
var classMatches = cssClass.match(/\b(ame-)?menu-icon-([^\s]+)\b/);
2472
+ //Dashicons and FontAwesome icons are set via the icon URL field, but they are actually CSS-based.
2473
+ var iconFontMatches = iconUrl && iconUrl.match('^\s*((?:dashicons|ame-fa)-[a-z0-9\-]+)\s*#x27;);
2474
2475
+ if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' && !iconFontMatches ) {
2476
var currentIcon = iconSelector.find('.ws_icon_option img[src="' + iconUrl + '"]').first().closest('.ws_icon_option');
2477
if ( currentIcon.length > 0 ) {
2478
+ selectedIcon = currentIcon.addClass('ws_selected_icon').show();
2479
} else {
2480
//Display and highlight the custom image.
2481
customImageOption.find('img').prop('src', iconUrl);
2482
customImageOption.addClass('ws_selected_icon').show().data('icon-url', iconUrl);
2483
+ selectedIcon = customImageOption;
2484
}
2485
+ } else if ( classMatches || iconFontMatches ) {
2486
+ //Highlight the icon that corresponds to the current CSS class or Dashicon/FontAwesome icon.
2487
+ var iconClass = iconFontMatches ? iconFontMatches[1] : ((classMatches[1] ? classMatches[1] : '') + 'icon-' + classMatches[2]);
2488
+ selectedIcon = iconSelector.find('.' + iconClass).closest('.ws_icon_option').addClass('ws_selected_icon');
2489
}
2490
2491
+ //Activate the tab that contains the icon.
2492
+ var activeTabId = selectedIcon.closest('.ws_tool_tab').prop('id'),
2493
+ activeTabItem = iconSelectorTabs.find('a[href="#' + activeTabId + '"]').closest('li');
2494
+ if (activeTabItem.length > 0) {
2495
+ iconSelectorTabs.tabs('option', 'active', activeTabItem.index());
2496
+ }
2497
2498
iconSelector.show();
2499
iconSelector.position({ //Requires jQuery UI.
2571
iconSelector.hide();
2572
});
2573
2574
//Hide the icon selector if the user clicks outside of it.
2575
//Exception: Clicks on "Select icon" buttons are handled above.
2576
$(document).on('mouseup', function(event) {
4169
});
4170
}
4171
4172
+ /******************************************************************
4173
+ Tooltips and hints
4174
+ ******************************************************************/
4175
+
4176
4177
//Set up tooltips
4178
$('.ws_tooltip_trigger').qtip({
4185
}
4186
});
4187
4188
+ //Set up menu field toltips.
4189
+ menuEditorNode.on('mouseenter click', '.ws_edit_field .ws_field_tooltip_trigger', function(event) {
4190
+ var $trigger = $(this),
4191
+ fieldName = $trigger.closest('.ws_edit_field').data('field_name');
4192
+
4193
+ if (knownMenuFields[fieldName].tooltip === null) {
4194
+ return;
4195
+ }
4196
+
4197
+ var tooltipText = 'Invalid tooltip';
4198
+ if (typeof knownMenuFields[fieldName].tooltip === 'string') {
4199
+ tooltipText = knownMenuFields[fieldName].tooltip;
4200
+ } else if (typeof knownMenuFields[fieldName].tooltip === 'function') {
4201
+ tooltipText = function() {
4202
+ var $theTrigger = $(this),
4203
+ menuItem = $theTrigger.closest('.ws_container').data('menu_item');
4204
+ return knownMenuFields[fieldName].tooltip(menuItem);
4205
+ }
4206
+ }
4207
+
4208
+ $trigger.qtip({
4209
+ overwrite: false,
4210
+ content: {
4211
+ text: tooltipText
4212
+ },
4213
+ show: {
4214
+ event: event.type,
4215
+ ready: true //Show immediately.
4216
+ },
4217
+ style: {
4218
+ classes: 'qtip qtip-rounded ws_tooltip_node'
4219
+ },
4220
+ hide: {
4221
+ fixed: true,
4222
+ delay: 300
4223
+ },
4224
+ position: {
4225
+ my: 'bottom center',
4226
+ at: 'top center'
4227
+ }
4228
+ }, event);
4229
+ });
4230
+
4231
//Set up the "additional permissions are available" tooltips.
4232
menuEditorNode.on('mouseenter click', '.ws_ext_permissions_indicator', function() {
4233
var $indicator = $(this);
4343
4344
var screenOptions = $('#ws-ame-screen-meta-contents');
4345
var hideSettingsCheckbox = screenOptions.find('#ws-hide-advanced-settings');
4346
hideSettingsCheckbox.prop('checked', wsEditorData.hideAdvancedSettings);
4347
4348
//Update editor state when settings change
4349
+ $('#ws-hide-advanced-settings').click(function(){
4350
wsEditorData.hideAdvancedSettings = hideSettingsCheckbox.prop('checked');
4351
4352
//Show/hide advanced settings dynamically as the user changes the setting.
4353
if ($(this).is(hideSettingsCheckbox)) {
4370
'_ajax_nonce' : wsEditorData.hideAdvancedSettingsNonce
4371
}
4372
);
4373
});
4374
4375
//Move our options into the screen meta panel
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.7
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.7.1
7
Author: Janis Elsts
8
Author URI: http://w-shadow.com/blog/
9
*/
modules/plugin-visibility/plugin-visibility.php CHANGED
@@ -308,11 +308,6 @@ class amePluginVisibility {
308
}
309
310
public function enqueueScripts() {
311
- wp_register_auto_versioned_script(
312
- 'knockout',
313
- plugins_url('js/knockout.js', $this->menuEditor->plugin_file)
314
- );
315
-
316
wp_register_auto_versioned_script(
317
'ame-plugin-visibility',
318
plugins_url('plugin-visibility.js', __FILE__),
308
}
309
310
public function enqueueScripts() {
311
wp_register_auto_versioned_script(
312
'ame-plugin-visibility',
313
plugins_url('plugin-visibility.js', __FILE__),
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.6-beta3
7
- Stable tag: 1.7
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,11 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
64
== Changelog ==
65
66
= 1.7 =
67
* Added a "Plugins" tab. It lets you hide specific plugins from other users. Note that this only affects the list on the "Plugins" page and tasks like editing plugin files, but it doesn't affect the admin menu.
68
* Tested up to WordPress 4.6-beta3.
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.6
7
+ Stable tag: 1.7.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.7.1 =
67
+ * Split the "required capability" field into two parts - a read-only field that shows the actual required capability, and an editable "extra capability" that you can use to restrict access to the menu.
68
+ * Added more detailed permission error messages. You can turn them off in the "Settings" tab by changing "Error verbosity level" to "Low".
69
+ * Tested up to WP 4.6.
70
+
71
= 1.7 =
72
* Added a "Plugins" tab. It lets you hide specific plugins from other users. Note that this only affects the list on the "Plugins" page and tasks like editing plugin files, but it doesn't affect the admin menu.
73
* Tested up to WordPress 4.6-beta3.