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*$');
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*$');
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.