Admin Menu Editor - Version 1.3.1

Version Description

  • Tested with WordPress 3.8.
  • Fixed several minor UI/layout issues related to the new 3.8 admin style.
  • Fixed a bug where moving an item to a plugin menu and then deactivating that plugin would cause the moved item to disappear.
  • Fixed deleted submenus not being restored if their original parent menu is no longer available.
  • Fixed a rare glitch where submenu separators added by certain other plugins would sometimes disappear.
  • Fixed a conflict with Shopp 1.2.9.
  • Made the plugin treat "users.php" and "profile.php" as the same parent menu. This fixes situations where it would be impossible to hide a "Users" submenu item from roles that don't have access to the "Users" menu and instead get a "Profile" menu.
  • Added extra logging for situations where a menu item is hidden because a higher-priority item with the same URL is also hidden.
  • Minor performance improvements.
Download this release

Release Info

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

Code changes from version 1.3 to 1.3.1

css/menu-editor.css CHANGED
@@ -102,7 +102,7 @@
102
  clear: both;
103
  display: block;
104
  margin: 4px;
105
- width: 120px;
106
  }
107
 
108
  #ws_menu_editor #ws_save_menu {
@@ -289,7 +289,7 @@
289
  #ws_menu_access_editor .ws_dropdown_button
290
  {
291
  width: 20px;
292
- height: 20px;
293
 
294
  margin: 1px 1px 1px 0;
295
  padding: 0;
@@ -561,6 +561,9 @@ select.ws_dropdown optgroup option {
561
  margin: 0;
562
  padding: 0;
563
  position: relative;
 
 
 
564
  }
565
 
566
  /* Current icon node (CSS class version, for the built-in WP icon sprites) */
@@ -611,12 +614,19 @@ select.ws_dropdown optgroup option {
611
 
612
  .ui-widget-overlay {
613
  background-color: black;
614
- position: absolute;
615
  left: 0;
616
  top: 0;
617
  opacity: 0.70;
618
  -moz-opacity: 0.70;
619
- filter: alpha(opacity=70);
 
 
 
 
 
 
 
620
  }
621
 
622
  .ui-dialog {
@@ -689,6 +699,8 @@ select.ws_dropdown optgroup option {
689
  height: 23px;
690
  text-align: right;
691
  margin-top: 20px;
 
 
692
  }
693
 
694
  .ws_dialog_buttons .button-primary {
@@ -718,6 +730,11 @@ select.ws_dropdown optgroup option {
718
  padding-top: 25px;
719
  }
720
 
 
 
 
 
 
721
  /************************************
722
  Menu access editor
723
  *************************************/
@@ -886,6 +903,8 @@ select.ws_dropdown optgroup option {
886
  }
887
 
888
  #ws_sidebar_pro_ad {
 
 
889
  margin-top: 5px;
890
  margin-left: 3px;
891
 
@@ -893,57 +912,4 @@ select.ws_dropdown optgroup option {
893
  right: 20px;
894
  bottom: 40px;
895
  z-index: 100;
896
- }
897
-
898
- /************************************
899
- Screen meta buttons
900
- *************************************/
901
-
902
- /* All buttons */
903
- #ws-pro-version-notice {
904
- float: right;
905
- height: 22px;
906
- padding: 0;
907
- margin: 0 0 0 6px;
908
- font-family: sans-serif;
909
-
910
- -moz-border-radius-bottomleft: 3px;
911
- -moz-border-radius-bottomright: 3px;
912
- -webkit-border-bottom-left-radius: 3px;
913
- -webkit-border-bottom-right-radius: 3px;
914
- border-bottom-left-radius: 3px;
915
- border-bottom-right-radius: 3px;
916
-
917
- background: #e3e3e3;
918
-
919
- border-right: 1px solid transparent;
920
- border-left: 1px solid transparent;
921
- border-bottom: 1px solid transparent;
922
- background-image: -ms-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* IE10 */
923
- background-image: -moz-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Firefox */
924
- background-image: -o-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Opera */
925
- background-image: -webkit-gradient(linear, left bottom, left top, from(#dfdfdf), to(#f1f1f1)); /* old Webkit */
926
- background-image: -webkit-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* new Webkit */
927
- background-image: linear-gradient(bottom, #dfdfdf, #f1f1f1); /* proposed W3C Markup */
928
- }
929
-
930
- #ws-pro-version-notice a.show-settings {
931
- background-image: none;
932
- padding:0 6px 0 6px;
933
- }
934
-
935
- /* "Upgrade to Pro" */
936
- #ws-pro-version-notice {
937
- background: #00C31F none;
938
- }
939
-
940
-
941
- #ws-pro-version-notice a.show-settings {
942
- font-weight: bold;
943
- color: #DEFFD8;
944
- text-shadow: none;
945
- }
946
-
947
- #ws-pro-version-notice a.show-settings:hover {
948
- color: white;
949
- }
102
  clear: both;
103
  display: block;
104
  margin: 4px;
105
+ width: 130px;
106
  }
107
 
108
  #ws_menu_editor #ws_save_menu {
289
  #ws_menu_access_editor .ws_dropdown_button
290
  {
291
  width: 20px;
292
+ height: 23px;
293
 
294
  margin: 1px 1px 1px 0;
295
  padding: 0;
561
  margin: 0;
562
  padding: 0;
563
  position: relative;
564
+
565
+ box-sizing: border-box;
566
+ height: 25px;
567
  }
568
 
569
  /* Current icon node (CSS class version, for the built-in WP icon sprites) */
614
 
615
  .ui-widget-overlay {
616
  background-color: black;
617
+ position: fixed;
618
  left: 0;
619
  top: 0;
620
  opacity: 0.70;
621
  -moz-opacity: 0.70;
622
+ filter: alpha(opacity=70);
623
+
624
+ width: 100%;
625
+ height: 100%;
626
+ }
627
+
628
+ .ui-front {
629
+ z-index: 100;
630
  }
631
 
632
  .ui-dialog {
699
  height: 23px;
700
  text-align: right;
701
  margin-top: 20px;
702
+ margin-bottom: 1px;
703
+ clear: both;
704
  }
705
 
706
  .ws_dialog_buttons .button-primary {
730
  padding-top: 25px;
731
  }
732
 
733
+ .ws_dont_show_again {
734
+ display: inline-block;
735
+ margin-top: 1em;
736
+ }
737
+
738
  /************************************
739
  Menu access editor
740
  *************************************/
903
  }
904
 
905
  #ws_sidebar_pro_ad {
906
+ min-width: 225px;
907
+
908
  margin-top: 5px;
909
  margin-left: 3px;
910
 
912
  right: 20px;
913
  bottom: 40px;
914
  z-index: 100;
915
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
css/screen-meta-old-wp.css ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /************************************
2
+ Screen meta buttons
3
+ for WP 3.7 and below
4
+ *************************************/
5
+
6
+ /* All buttons */
7
+ .custom-screen-meta-link-wrap {
8
+ float: right;
9
+ height: 22px;
10
+ padding: 0;
11
+ margin: 0 0 0 6px;
12
+ font-family: sans-serif;
13
+ -moz-border-radius-bottomleft: 3px;
14
+ -moz-border-radius-bottomright: 3px;
15
+ -webkit-border-bottom-left-radius: 3px;
16
+ -webkit-border-bottom-right-radius: 3px;
17
+ border-bottom-left-radius: 3px;
18
+ border-bottom-right-radius: 3px;
19
+
20
+ background: #e3e3e3;
21
+
22
+ border-right: 1px solid transparent;
23
+ border-left: 1px solid transparent;
24
+ border-bottom: 1px solid transparent;
25
+ background-image: -ms-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* IE10 */
26
+ background-image: -moz-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Firefox */
27
+ background-image: -o-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Opera */
28
+ background-image: -webkit-gradient(linear, left bottom, left top, from(#dfdfdf), to(#f1f1f1)); /* old Webkit */
29
+ background-image: -webkit-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* new Webkit */
30
+ background-image: linear-gradient(bottom, #dfdfdf, #f1f1f1); /* proposed W3C Markup */
31
+ }
32
+
33
+ #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
34
+ #screen-meta-links .custom-screen-meta-link-wrap a.custom-screen-meta-link
35
+ {
36
+ background-image: none;
37
+ padding: 0 6px 0 6px;
38
+ }
39
+
40
+ #screen-meta-links a.custom-screen-meta-link::after {
41
+ display: none;
42
+ }
43
+
44
+ /* "Upgrade to Pro" */
45
+ #ws-pro-version-notice {
46
+ background: #00C31F none;
47
+ }
48
+
49
+ #ws-pro-version-notice a.show-settings {
50
+ font-weight: bold;
51
+ color: #DEFFD8;
52
+ text-shadow: none;
53
+ }
54
+
55
+ #ws-pro-version-notice a.show-settings:hover {
56
+ color: white;
57
+ }
css/screen-meta.css ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /************************************
2
+ Screen meta buttons
3
+ for WP 3.8+
4
+ *************************************/
5
+
6
+ /* All buttons */
7
+ .custom-screen-meta-link-wrap {
8
+ float: right;
9
+ height: 28px;
10
+ margin: 0 0 0 6px;
11
+
12
+ border: 1px solid #ddd;
13
+ border-top: none;
14
+ background: #fff;
15
+ -webkit-box-shadow: 0px 1px 1px -1px rgba(0,0,0,0.1);
16
+ box-shadow: 0px 1px 1px -1px rgba(0,0,0,0.1);
17
+ }
18
+
19
+ #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
20
+ #screen-meta-links .custom-screen-meta-link-wrap a.custom-screen-meta-link
21
+ {
22
+ padding: 3px 16px 3px 16px;
23
+ }
24
+
25
+ #screen-meta-links a.custom-screen-meta-link::after {
26
+ display: none;
27
+ }
28
+
29
+ /* "Upgrade to Pro" */
30
+ #ws-pro-version-notice {
31
+ background-color: #00C31F;
32
+ border-color: #0a0;
33
+ }
34
+
35
+ #ws-pro-version-notice a.show-settings {
36
+ font-weight: bold;
37
+ color: #DEFFD8;
38
+ text-shadow: none;
39
+ }
40
+
41
+ #ws-pro-version-notice a.show-settings:hover {
42
+ color: white;
43
+ }
includes/editor-page.php CHANGED
@@ -24,8 +24,8 @@ if ( !apply_filters('admin_menu_editor_is_pro', false) ){
24
  <script type="text/javascript">
25
  (function($){
26
  $('#screen-meta-links').append(
27
- '<div id="ws-pro-version-notice">' +
28
- '<a href="http://adminmenueditor.com/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=top_upgrade_link&utm_campaign=Plugins" id="ws-pro-version-notice-link" class="show-settings" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
29
  '</div>'
30
  );
31
  })(jQuery);
@@ -104,7 +104,11 @@ endif;
104
  <div class="ws_separator">&nbsp;</div>
105
 
106
  <a id='ws_new_menu' class='ws_button' href='javascript:void(0)' title='New menu'><img src='<?php echo $icons['new']; ?>' alt="New menu" /></a>
107
- <a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
 
 
 
 
108
  <a id='ws_delete_menu' class='ws_button' href='javascript:void(0)' title='Delete menu'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu" /></a>
109
 
110
  <div class="ws_separator">&nbsp;</div>
@@ -136,7 +140,9 @@ endif;
136
  <div class="ws_separator">&nbsp;</div>
137
 
138
  <a id='ws_new_item' class='ws_button' href='javascript:void(0)' title='New menu item'><img src='<?php echo $icons['new']; ?>' alt="New menu item" /></a>
139
- <a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
 
 
140
  <a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu item" /></a>
141
 
142
  <div class="ws_separator">&nbsp;</div>
@@ -316,6 +322,41 @@ endif;
316
  </label>
317
  </span>
318
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  <script type='text/javascript'>
320
  var defaultMenu = <?php echo $editor_data['default_menu_js']; ?>;
321
  var customMenu = <?php echo $editor_data['custom_menu_js']; ?>;
24
  <script type="text/javascript">
25
  (function($){
26
  $('#screen-meta-links').append(
27
+ '<div id="ws-pro-version-notice" class="custom-screen-meta-link-wrap">' +
28
+ '<a href="http://adminmenueditor.com/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=top_upgrade_link&utm_campaign=Plugins" id="ws-pro-version-notice-link" class="show-settings custom-screen-meta-link" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
29
  '</div>'
30
  );
31
  })(jQuery);
104
  <div class="ws_separator">&nbsp;</div>
105
 
106
  <a id='ws_new_menu' class='ws_button' href='javascript:void(0)' title='New menu'><img src='<?php echo $icons['new']; ?>' alt="New menu" /></a>
107
+
108
+ <?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
109
+ <a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
110
+ <?php endif; ?>
111
+
112
  <a id='ws_delete_menu' class='ws_button' href='javascript:void(0)' title='Delete menu'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu" /></a>
113
 
114
  <div class="ws_separator">&nbsp;</div>
140
  <div class="ws_separator">&nbsp;</div>
141
 
142
  <a id='ws_new_item' class='ws_button' href='javascript:void(0)' title='New menu item'><img src='<?php echo $icons['new']; ?>' alt="New menu item" /></a>
143
+ <?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
144
+ <a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
145
+ <?php endif; ?>
146
  <a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu item" /></a>
147
 
148
  <div class="ws_separator">&nbsp;</div>
322
  </label>
323
  </span>
324
 
325
+
326
+ <!-- Confirmation dialog when hiding "Dashboard -> Home" -->
327
+ <div id="ws-ame-dashboard-hide-confirmation" style="display: none;">
328
+ <span>
329
+ Hiding <em>Dashboard -> Home</em> may prevent users with the selected role from logging in!
330
+ Are you sure you want to do it?
331
+ </span>
332
+
333
+ <h4>Explanation</h4>
334
+ <p>
335
+ WordPress automatically redirects users to the <em>Dashboard -> Home</em> page upon successful login.
336
+ If you hide this page, users will get an "insufficient permissions" error when they log in
337
+ due to being redirected to a hidden page. As a result, it will look like their login failed.
338
+ </p>
339
+
340
+ <h4>Recommendations</h4>
341
+ <p>
342
+ You can use a plugin like <a href="http://wordpress.org/plugins/peters-login-redirect/">Peter's Login Redirect</a>
343
+ to redirect specific roles to different pages.
344
+ </p>
345
+
346
+ <div class="ws_dialog_buttons">
347
+ <?php
348
+ submit_button('Hide the menu', 'primary', 'ws_confirm_menu_hiding', false);
349
+ submit_button('Leave it visible', 'secondary', 'ws_cancel_menu_hiding', false);
350
+ ?>
351
+ </div>
352
+
353
+ <label class="ws_dont_show_again">
354
+ <input type="checkbox" id="ws-ame-disable-dashboard-hide-confirmation">
355
+ Don't show this message again
356
+ </label>
357
+ </div>
358
+
359
+
360
  <script type='text/javascript'>
361
  var defaultMenu = <?php echo $editor_data['default_menu_js']; ?>;
362
  var customMenu = <?php echo $editor_data['custom_menu_js']; ?>;
includes/menu-editor-core.php CHANGED
@@ -69,6 +69,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
69
  private $cached_custom_menu = null; //Cached, non-merged version of the custom menu. Used by load_custom_menu().
70
  private $cached_virtual_caps = null;//List of virtual caps. Used by get_virtual_caps().
71
 
 
 
 
72
  //Our personal copy of the request vars, without any "magic quotes".
73
  private $post = array();
74
  private $get = array();
@@ -96,6 +99,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
96
  'allowed_user_id' => null,
97
  //The user who can see this plugin on the "Plugins" page. By default all admins can see it.
98
  'plugins_page_allowed_user_id' => null,
 
 
 
 
 
 
99
  );
100
  $this->serialize_with_json = false; //(Don't) store the options in JSON format
101
 
@@ -105,10 +114,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
105
  $this->magic_hook_priority = 99999;
106
 
107
  //AJAXify screen options
108
- add_action('wp_ajax_ws_ame_save_screen_options', array(&$this,'ajax_save_screen_options'));
109
 
110
- //AJAXify hints
111
  add_action('wp_ajax_ws_ame_hide_hint', array($this, 'ajax_hide_hint'));
 
 
 
 
112
 
113
  //Make sure we have access to the original, un-mangled request data.
114
  //This is necessary because WordPress will stupidly apply "magic quotes"
@@ -123,6 +136,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
123
 
124
  //User survey
125
  add_action('admin_notices', array($this, 'display_survey_notice'));
 
 
 
126
  }
127
 
128
  function init_finish() {
@@ -182,7 +198,20 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
182
  function hook_admin_menu(){
183
  global $menu, $submenu;
184
 
185
- //Menu reset (for emergencies). Executed by accessing http://example.com/wp-admin/?reset_admin_menu=1
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  $reset_requested = isset($this->get['reset_admin_menu']) && $this->get['reset_admin_menu'];
187
  if ( $reset_requested && $this->current_user_can_edit_menu() ){
188
  $this->set_custom_menu(null);
@@ -235,7 +264,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
235
 
236
  //Convert our custom menu to the $menu + $submenu structure used by WP.
237
  //Note: This method sets up multiple internal fields and may cause side-effects.
 
238
  $this->build_custom_wp_menu($this->merged_custom_menu['tree']);
 
239
 
240
  if ( !$this->user_can_access_current_page() ) {
241
  $this->log_security_note('DENY access.');
@@ -278,7 +309,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
278
 
279
  $menu = $this->custom_wp_menu;
280
  $submenu = $this->custom_wp_submenu;
 
 
281
  list($menu, $submenu) = $this->filter_menu($menu, $submenu);
 
282
 
283
  return $parent_file;
284
  }
@@ -496,6 +530,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
496
  'selectedActor' => isset($this->get['selected_actor']) ? strval($this->get['selected_actor']) : null,
497
 
498
  'showHints' => $this->get_hint_visibility(),
 
 
499
 
500
  'isDemoMode' => defined('IS_DEMO_MODE'),
501
  'isMasterMode' => defined('IS_MASTER_MODE'),
@@ -559,6 +595,17 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
559
  array('menu-editor-base-style')
560
  );
561
 
 
 
 
 
 
 
 
 
 
 
 
562
  wp_enqueue_style('menu-editor-colours-classic');
563
  }
564
 
@@ -585,6 +632,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
585
 
586
  $this->cached_custom_menu = null;
587
  $this->cached_virtual_caps = null;
 
588
  }
589
 
590
  /**
@@ -746,6 +794,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
746
  */
747
  function menu_merge($tree){
748
  //Iterate over all menus and submenus and look up default values
 
 
749
  foreach ($tree as &$topmenu){
750
 
751
  if ( !ameMenuItem::get($topmenu, 'custom') ) {
@@ -779,6 +829,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
779
  //Yes, load defaults from that item
780
  $item['defaults'] = $this->item_templates[$template_id]['defaults'];
781
  $this->item_templates[$template_id]['used'] = true;
 
 
 
 
782
  } else if ( empty($item['separator']) ) {
783
  //Record as missing, unless it's a menu separator
784
  $item['missing'] = true;
@@ -787,6 +841,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
787
  $temp = $this->set_final_menu_capability($temp);
788
  $this->add_access_lookup($temp, 'submenu', true);
789
  }
 
 
 
790
  }
791
  }
792
  }
@@ -838,13 +895,25 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
838
  $tree[$template['defaults']['parent']]['items'][] = $entry;
839
  } else {
840
  //This can happen if the original parent menu has been moved to a submenu.
841
- //Todo: Handle this unusual situation.
842
  }
843
  } else {
844
  $tree[$template['defaults']['file']] = $entry;
845
  }
846
  }
847
 
 
 
 
 
 
 
 
 
 
 
 
 
848
  //Resort the tree to ensure the found items are in the right spots
849
  $tree = ameMenu::sort_menu_tree($tree);
850
 
@@ -976,9 +1045,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
976
  //Convert the prepared tree to the internal WordPress format.
977
  foreach($new_tree as $topmenu) {
978
  $trueAccess = isset($this->page_access_lookup[$topmenu['url']]) ? $this->page_access_lookup[$topmenu['url']] : null;
979
- if ( $trueAccess === 'do_not_allow' ) {
980
  $topmenu['access_level'] = $trueAccess;
 
 
 
 
 
 
 
 
 
981
  }
 
982
  if ( !isset($this->reverse_item_lookup[$topmenu['url']]) ) { //Prefer sub-menus.
983
  $this->reverse_item_lookup[$topmenu['url']] = $topmenu;
984
  }
@@ -987,9 +1066,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
987
 
988
  foreach($topmenu['items'] as $item) {
989
  $trueAccess = isset($this->page_access_lookup[$item['url']]) ? $this->page_access_lookup[$item['url']] : null;
990
- if ( $trueAccess === 'do_not_allow' ) {
991
  $item['access_level'] = $trueAccess;
 
 
 
 
 
 
 
 
 
992
  }
 
993
  $this->reverse_item_lookup[$item['url']] = $item;
994
  $new_submenu[$topmenu['file']][] = $this->convert_to_wp_format($item);
995
  }
@@ -1083,7 +1172,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1083
  }
1084
 
1085
  //Used later to determine the current page based on URL.
1086
- $item['url'] = ameMenuItem::generate_url($item['file'], $parent);
 
 
 
1087
 
1088
  //Convert relative URls to fully qualified ones. This prevents problems with WordPress
1089
  //incorrectly converting "index.php?page=xyz" to, say, "tools.php?page=index.php?page=xyz"
@@ -1136,15 +1228,30 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1136
  $user_has_access = true;
1137
  $cap_to_use = '';
1138
  if ( !empty($item['access_level']) ) {
1139
- $user_has_cap = $this->current_user_can($item['access_level']);
1140
- $user_has_access = $user_has_access && $user_has_cap;
1141
  $cap_to_use = $item['access_level'];
1142
 
1143
- $item['access_check_log'][] = sprintf(
1144
- 'Required capability: %1$s. User %2$s this capability.',
1145
- htmlentities($cap_to_use),
1146
- $user_has_cap ? 'HAS' : 'DOES NOT have'
1147
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1148
  } else {
1149
  $item['access_check_log'][] = '- No required capability set.';
1150
  }
@@ -1191,6 +1298,18 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1191
  $this->handle_form_submission($this->post, $action);
1192
  }
1193
 
 
 
 
 
 
 
 
 
 
 
 
 
1194
  $sub_section = isset($this->get['sub_section']) ? $this->get['sub_section'] : null;
1195
  if ( $sub_section === 'settings' ) {
1196
  $this->display_plugin_settings_ui();
@@ -1281,6 +1400,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1281
  //Hide some menu options by default.
1282
  $this->options['hide_advanced_settings'] = !empty($this->post['hide_advanced_settings']);
1283
 
 
 
 
 
 
1284
  $this->save_options();
1285
  wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
1286
  }
@@ -1293,6 +1417,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1293
  'images_url' => plugins_url('images', $this->plugin_file),
1294
  'hide_advanced_settings' => $this->options['hide_advanced_settings'],
1295
  'settings_page_url' => $this->get_settings_page_url(),
 
1296
  );
1297
 
1298
  //Build a tree struct. for the default menu
@@ -1346,8 +1471,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1346
  */
1347
  private function display_plugin_settings_ui() {
1348
  //These variables are used by settings-page.php.
 
1349
  $settings = $this->options;
 
1350
  $settings_page_url = $this->get_settings_page_url();
 
1351
  $editor_page_url = admin_url($this->settings_link);
1352
 
1353
  require dirname(__FILE__) . '/settings-page.php';
@@ -1408,7 +1536,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1408
  }
1409
 
1410
  foreach($custom_menu['tree'] as $item) {
1411
- $caps = array_merge_recursive($caps, $this->get_virtual_caps_for($item));
1412
  }
1413
 
1414
  $this->cached_virtual_caps = $caps;
@@ -1431,12 +1559,25 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1431
  }
1432
 
1433
  foreach($item['items'] as $sub_item) {
1434
- $caps = array_merge_recursive($caps, $this->get_virtual_caps_for($sub_item));
1435
  }
1436
 
1437
  return $caps;
1438
  }
1439
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1440
  /**
1441
  * Create a virtual 'super_admin' capability that only super admins have.
1442
  * This function accomplishes that by by filtering 'user_has_cap' calls.
@@ -1508,6 +1649,17 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1508
  update_user_meta($user->ID, 'ame_show_hints', $show_hints);
1509
  }
1510
 
 
 
 
 
 
 
 
 
 
 
 
1511
  /**
1512
  * Enqueue a script that fixes a bug where pages moved to a different menu
1513
  * would not be highlighted properly when the user visits them.
@@ -1561,7 +1713,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1561
  * @return bool
1562
  */
1563
  private function current_user_can($capability) {
1564
- return apply_filters('admin_menu_editor-current_user_can', current_user_can($capability), $capability);
 
 
 
 
 
 
 
 
 
 
 
 
1565
  }
1566
 
1567
  /**
@@ -1600,6 +1764,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1600
  $base_site_url = $matches[1];
1601
  }
1602
 
 
 
 
 
 
 
1603
  $current_url = $base_site_url . remove_query_arg('___ame_dummy_param___');
1604
  $this->log_security_note(sprintf('Current URL: "%s"', htmlentities($current_url)));
1605
 
@@ -1612,7 +1782,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1612
  if ( substr($item_url, 0, 1) == '/' ) {
1613
  $item_url = $base_site_url . $item_url;
1614
  } else {
1615
- $item_url = admin_url($item_url);
 
 
 
 
1616
  }
1617
  }
1618
  $item_url = $this->parse_url($item_url);
@@ -1735,7 +1909,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1735
 
1736
  $display_notice = $this->options['display_survey_notice'] && $this->current_user_can_edit_menu();
1737
  if ( isset($this->options['first_install_time']) ) {
1738
- $minimum_usage_period = 3*24*3600;
1739
  $display_notice = $display_notice && ((time() - $this->options['first_install_time']) > $minimum_usage_period);
1740
  }
1741
 
@@ -1753,7 +1927,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1753
  $free_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dERyeDk0OWhlbkxYcEY4QTNaMnlTQUE6MQ';
1754
  $pro_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dHl4MnlHaVI3NE5JdVFDWG01SkRKTWc6MA';
1755
 
1756
- if ( apply_filters('admin_menu_editor_is_pro', false) ) {
1757
  $survey_url = $pro_survey_url;
1758
  } else {
1759
  $survey_url = $free_survey_url;
@@ -1952,4 +2126,49 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1952
  return $name;
1953
  }
1954
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1955
  } //class
69
  private $cached_custom_menu = null; //Cached, non-merged version of the custom menu. Used by load_custom_menu().
70
  private $cached_virtual_caps = null;//List of virtual caps. Used by get_virtual_caps().
71
 
72
+ private $cached_user_caps = array(); //A cache of the current user's capabilities. Used only in very specific places.
73
+ private $user_cap_cache_enabled = false;
74
+
75
  //Our personal copy of the request vars, without any "magic quotes".
76
  private $post = array();
77
  private $get = array();
99
  'allowed_user_id' => null,
100
  //The user who can see this plugin on the "Plugins" page. By default all admins can see it.
101
  'plugins_page_allowed_user_id' => null,
102
+
103
+ 'show_deprecated_hide_button' => null,
104
+ 'dashboard_hiding_confirmation_enabled' => true,
105
+
106
+ //Enable/disable the admin notice that tells the user where the plugin settings menu is.
107
+ 'show_plugin_menu_notice' => true,
108
  );
109
  $this->serialize_with_json = false; //(Don't) store the options in JSON format
110
 
114
  $this->magic_hook_priority = 99999;
115
 
116
  //AJAXify screen options
117
+ add_action('wp_ajax_ws_ame_save_screen_options', array($this,'ajax_save_screen_options'));
118
 
119
+ //AJAXify hints and warnings
120
  add_action('wp_ajax_ws_ame_hide_hint', array($this, 'ajax_hide_hint'));
121
+ add_action(
122
+ 'wp_ajax_ws_ame_disable_dashboard_hiding_confirmation',
123
+ array($this, 'ajax_disable_dashboard_hiding_confirmation')
124
+ );
125
 
126
  //Make sure we have access to the original, un-mangled request data.
127
  //This is necessary because WordPress will stupidly apply "magic quotes"
136
 
137
  //User survey
138
  add_action('admin_notices', array($this, 'display_survey_notice'));
139
+
140
+ //Tell first-time users where they can find the plugin settings page.
141
+ add_action('all_admin_notices', array($this, 'display_plugin_menu_notice'));
142
  }
143
 
144
  function init_finish() {
198
  function hook_admin_menu(){
199
  global $menu, $submenu;
200
 
201
+ //Compatibility fix for Shopp 1.2.9. This plugin has an "admin_menu" hook (Flow::menu) that adds another
202
+ //"admin_menu" hook (AdminFlow::taxonomies) when it runs. Basically, it indirectly modifies the global
203
+ //$wp_filters['admin_menu'] array while WordPress is iterating it (nasty!). Due to how PHP arrays are
204
+ //implemented and how do_action() works, this second hook is the very last one to run, even after hooks
205
+ //with a lower priority.
206
+ //The only way we can see the changes made by the second hook is to do the same thing.
207
+ static $firstRunSkipped = false;
208
+ if ( !$firstRunSkipped && class_exists('Flow') ) {
209
+ add_action('admin_menu', array($this, 'hook_admin_menu'), $this->magic_hook_priority + 1);
210
+ $firstRunSkipped = true;
211
+ return;
212
+ }
213
+
214
+ //Menu reset (for emergencies). Executed by accessing http://example.com/wp-admin/?reset_admin_menu=1
215
  $reset_requested = isset($this->get['reset_admin_menu']) && $this->get['reset_admin_menu'];
216
  if ( $reset_requested && $this->current_user_can_edit_menu() ){
217
  $this->set_custom_menu(null);
264
 
265
  //Convert our custom menu to the $menu + $submenu structure used by WP.
266
  //Note: This method sets up multiple internal fields and may cause side-effects.
267
+ $this->user_cap_cache_enabled = true;
268
  $this->build_custom_wp_menu($this->merged_custom_menu['tree']);
269
+ $this->user_cap_cache_enabled = false;
270
 
271
  if ( !$this->user_can_access_current_page() ) {
272
  $this->log_security_note('DENY access.');
309
 
310
  $menu = $this->custom_wp_menu;
311
  $submenu = $this->custom_wp_submenu;
312
+
313
+ $this->user_cap_cache_enabled = true;
314
  list($menu, $submenu) = $this->filter_menu($menu, $submenu);
315
+ $this->user_cap_cache_enabled = false;
316
 
317
  return $parent_file;
318
  }
530
  'selectedActor' => isset($this->get['selected_actor']) ? strval($this->get['selected_actor']) : null,
531
 
532
  'showHints' => $this->get_hint_visibility(),
533
+ 'dashboardHidingConfirmationEnabled' => $this->options['dashboard_hiding_confirmation_enabled'],
534
+ 'disableDashboardConfirmationNonce' => wp_create_nonce('ws_ame_disable_dashboard_hiding_confirmation'),
535
 
536
  'isDemoMode' => defined('IS_DEMO_MODE'),
537
  'isMasterMode' => defined('IS_MASTER_MODE'),
595
  array('menu-editor-base-style')
596
  );
597
 
598
+ //WordPress introduced a new screen meta button style in WP 3.8.
599
+ //We have two different stylesheets - one for 3.8+ and one for backwards compatibility.
600
+ wp_register_auto_versioned_style('menu-editor-screen-meta', plugins_url('css/screen-meta.css', $this->plugin_file));
601
+ wp_register_auto_versioned_style('menu-editor-screen-meta-old', plugins_url('css/screen-meta-old-wp.css', $this->plugin_file));
602
+
603
+ if ( isset($GLOBALS['wp_version']) && version_compare($GLOBALS['wp_version'], '3.8-RC1', '<') ) {
604
+ wp_enqueue_style('menu-editor-screen-meta-old');
605
+ } else {
606
+ wp_enqueue_style('menu-editor-screen-meta');
607
+ }
608
+
609
  wp_enqueue_style('menu-editor-colours-classic');
610
  }
611
 
632
 
633
  $this->cached_custom_menu = null;
634
  $this->cached_virtual_caps = null;
635
+ $this->cached_user_caps = array();
636
  }
637
 
638
  /**
794
  */
795
  function menu_merge($tree){
796
  //Iterate over all menus and submenus and look up default values
797
+ //Also flag used and missing items.
798
+ $orphans = array();
799
  foreach ($tree as &$topmenu){
800
 
801
  if ( !ameMenuItem::get($topmenu, 'custom') ) {
829
  //Yes, load defaults from that item
830
  $item['defaults'] = $this->item_templates[$template_id]['defaults'];
831
  $this->item_templates[$template_id]['used'] = true;
832
+ //We must move orphaned items elsewhere. Use the default location if possible.
833
+ if ( isset($topmenu['missing']) && $topmenu['missing'] ) {
834
+ $orphans[] = $item;
835
+ }
836
  } else if ( empty($item['separator']) ) {
837
  //Record as missing, unless it's a menu separator
838
  $item['missing'] = true;
841
  $temp = $this->set_final_menu_capability($temp);
842
  $this->add_access_lookup($temp, 'submenu', true);
843
  }
844
+ } else {
845
+ //What if the parent of this custom item is missing?
846
+ //Right now the custom item will just disappear.
847
  }
848
  }
849
  }
895
  $tree[$template['defaults']['parent']]['items'][] = $entry;
896
  } else {
897
  //This can happen if the original parent menu has been moved to a submenu.
898
+ $tree[$template['defaults']['file']] = $entry;
899
  }
900
  } else {
901
  $tree[$template['defaults']['file']] = $entry;
902
  }
903
  }
904
 
905
+ //Move orphaned items back to their original parents.
906
+ foreach($orphans as $item) {
907
+ $defaultParent = !empty($item['defaults']['parent']) ? $item['defaults']['parent'] : null;
908
+ if ( isset($defaultParent) && isset($tree[$defaultParent]) ) {
909
+ $tree[$defaultParent]['items'][] = $item;
910
+ } else {
911
+ //This can happen if the parent has been moved to a submenu.
912
+ //Just put the orphan at the bottom of the menu.
913
+ $tree[$item['defaults']['file']] = $item;
914
+ }
915
+ }
916
+
917
  //Resort the tree to ensure the found items are in the right spots
918
  $tree = ameMenu::sort_menu_tree($tree);
919
 
1045
  //Convert the prepared tree to the internal WordPress format.
1046
  foreach($new_tree as $topmenu) {
1047
  $trueAccess = isset($this->page_access_lookup[$topmenu['url']]) ? $this->page_access_lookup[$topmenu['url']] : null;
1048
+ if ( ($trueAccess === 'do_not_allow') && ($topmenu['access_level'] !== $trueAccess) ) {
1049
  $topmenu['access_level'] = $trueAccess;
1050
+ if ( isset($topmenu['access_check_log']) ) {
1051
+ $topmenu['access_check_log'][] = sprintf(
1052
+ '+ Override: There is a hidden menu item with the same URL (%1$s) but a higher priority.' .
1053
+ ' Setting the capability to "%2$s".',
1054
+ $topmenu['url'],
1055
+ $trueAccess
1056
+ );
1057
+ $topmenu['access_check_log'][] = str_repeat('=', 79);
1058
+ }
1059
  }
1060
+
1061
  if ( !isset($this->reverse_item_lookup[$topmenu['url']]) ) { //Prefer sub-menus.
1062
  $this->reverse_item_lookup[$topmenu['url']] = $topmenu;
1063
  }
1066
 
1067
  foreach($topmenu['items'] as $item) {
1068
  $trueAccess = isset($this->page_access_lookup[$item['url']]) ? $this->page_access_lookup[$item['url']] : null;
1069
+ if ( ($trueAccess === 'do_not_allow') && ($item['access_level'] !== $trueAccess) ) {
1070
  $item['access_level'] = $trueAccess;
1071
+ if ( isset($item['access_check_log']) ) {
1072
+ $item['access_check_log'][] = sprintf(
1073
+ '+ Override: There is a hidden menu item with the same URL (%1$s) but a higher priority.' .
1074
+ ' Setting the capability to "%2$s".',
1075
+ $item['url'],
1076
+ $trueAccess
1077
+ );
1078
+ $item['access_check_log'][] = str_repeat('=', 79);
1079
+ }
1080
  }
1081
+
1082
  $this->reverse_item_lookup[$item['url']] = $item;
1083
  $new_submenu[$topmenu['file']][] = $this->convert_to_wp_format($item);
1084
  }
1172
  }
1173
 
1174
  //Used later to determine the current page based on URL.
1175
+ if ( empty($item['url']) ) {
1176
+ $original_parent = !empty($item['defaults']['parent']) ? $item['defaults']['parent'] : $parent;
1177
+ $item['url'] = ameMenuItem::generate_url($item['file'], $original_parent);
1178
+ }
1179
 
1180
  //Convert relative URls to fully qualified ones. This prevents problems with WordPress
1181
  //incorrectly converting "index.php?page=xyz" to, say, "tools.php?page=index.php?page=xyz"
1228
  $user_has_access = true;
1229
  $cap_to_use = '';
1230
  if ( !empty($item['access_level']) ) {
 
 
1231
  $cap_to_use = $item['access_level'];
1232
 
1233
+ if ( isset($item['user_has_access_level']) ) {
1234
+ //The "custom_admin_menu_capability" filter has already determined whether this user should
1235
+ //have the required capability, so checking it again would be redundant. This usually only
1236
+ //applies to the Pro version which uses that filter in extras.php.
1237
+ $user_has_cap = $item['user_has_access_level'];
1238
+
1239
+ $item['access_check_log'][] = sprintf(
1240
+ 'Skipping a "%1$s" capability check because we\'ve already determined that the current user %2$s access.',
1241
+ htmlentities($cap_to_use),
1242
+ $user_has_cap ? 'should have' : 'should not have'
1243
+ );
1244
+ } else {
1245
+ $user_has_cap = $this->current_user_can($cap_to_use);
1246
+ $item['access_check_log'][] = sprintf(
1247
+ 'Required capability: %1$s. User %2$s this capability.',
1248
+ htmlentities($cap_to_use),
1249
+ $user_has_cap ? 'HAS' : 'DOES NOT have'
1250
+ );
1251
+ }
1252
+
1253
+ $user_has_access = $user_has_access && $user_has_cap;
1254
+
1255
  } else {
1256
  $item['access_check_log'][] = '- No required capability set.';
1257
  }
1298
  $this->handle_form_submission($this->post, $action);
1299
  }
1300
 
1301
+ //By default, show the "Hide" button only if the user has already hidden something with it,
1302
+ //or if they're using the free version. Pro users should use role permissions instead, but can
1303
+ //explicitly enable the button if they want.
1304
+ if ( !isset($this->options['show_deprecated_hide_button']) ) {
1305
+ if ( $this->is_pro_version() ) {
1306
+ $this->options['show_deprecated_hide_button'] = ameMenu::has_hidden_items($this->merged_custom_menu);
1307
+ $this->save_options();
1308
+ } else {
1309
+ $this->options['show_deprecated_hide_button'] = true;
1310
+ }
1311
+ }
1312
+
1313
  $sub_section = isset($this->get['sub_section']) ? $this->get['sub_section'] : null;
1314
  if ( $sub_section === 'settings' ) {
1315
  $this->display_plugin_settings_ui();
1400
  //Hide some menu options by default.
1401
  $this->options['hide_advanced_settings'] = !empty($this->post['hide_advanced_settings']);
1402
 
1403
+ //Enable the now-obsolete "Hide" button.
1404
+ if ( $this->is_pro_version() ) {
1405
+ $this->options['show_deprecated_hide_button'] = !empty($this->post['show_deprecated_hide_button']);
1406
+ }
1407
+
1408
  $this->save_options();
1409
  wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
1410
  }
1417
  'images_url' => plugins_url('images', $this->plugin_file),
1418
  'hide_advanced_settings' => $this->options['hide_advanced_settings'],
1419
  'settings_page_url' => $this->get_settings_page_url(),
1420
+ 'show_deprecated_hide_button' => $this->options['show_deprecated_hide_button'],
1421
  );
1422
 
1423
  //Build a tree struct. for the default menu
1471
  */
1472
  private function display_plugin_settings_ui() {
1473
  //These variables are used by settings-page.php.
1474
+ /** @noinspection PhpUnusedLocalVariableInspection */
1475
  $settings = $this->options;
1476
+ /** @noinspection PhpUnusedLocalVariableInspection */
1477
  $settings_page_url = $this->get_settings_page_url();
1478
+ /** @noinspection PhpUnusedLocalVariableInspection */
1479
  $editor_page_url = admin_url($this->settings_link);
1480
 
1481
  require dirname(__FILE__) . '/settings-page.php';
1536
  }
1537
 
1538
  foreach($custom_menu['tree'] as $item) {
1539
+ $caps = self::array_replace_recursive($caps, $this->get_virtual_caps_for($item));
1540
  }
1541
 
1542
  $this->cached_virtual_caps = $caps;
1559
  }
1560
 
1561
  foreach($item['items'] as $sub_item) {
1562
+ $caps = self::array_replace_recursive($caps, $this->get_virtual_caps_for($sub_item));
1563
  }
1564
 
1565
  return $caps;
1566
  }
1567
 
1568
+ private static function array_replace_recursive($array1, $array2) {
1569
+ if ( function_exists('array_replace_recursive') ) {
1570
+ return array_replace_recursive($array1, $array2);
1571
+ }
1572
+ foreach($array2 as $key => $value) {
1573
+ if ( is_array($value) && isset($array1[$key]) && is_array($array1[$key]) ) {
1574
+ $value = self::array_replace_recursive($array1[$key], $value);
1575
+ }
1576
+ $array1[$key] = $value;
1577
+ }
1578
+ return $array1;
1579
+ }
1580
+
1581
  /**
1582
  * Create a virtual 'super_admin' capability that only super admins have.
1583
  * This function accomplishes that by by filtering 'user_has_cap' calls.
1649
  update_user_meta($user->ID, 'ame_show_hints', $show_hints);
1650
  }
1651
 
1652
+ /**
1653
+ * AJAX callback for permanently hiding the "are you sure you want to hide the Dashboard?" warning.
1654
+ */
1655
+ public function ajax_disable_dashboard_hiding_confirmation() {
1656
+ if (!check_ajax_referer('ws_ame_disable_dashboard_hiding_confirmation', false, false) || !$this->current_user_can_edit_menu()){
1657
+ die("You don't have sufficient permissions to do that.");
1658
+ }
1659
+ $this->options['dashboard_hiding_confirmation_enabled'] = false;
1660
+ $this->save_options();
1661
+ }
1662
+
1663
  /**
1664
  * Enqueue a script that fixes a bug where pages moved to a different menu
1665
  * would not be highlighted properly when the user visits them.
1713
  * @return bool
1714
  */
1715
  private function current_user_can($capability) {
1716
+ //WP core uses a special "do_not_allow" capability in a dozen or so places to explicitly deny access.
1717
+ //Even multisite super admins do not have this cap. We can return early here.
1718
+ if ( $capability === 'do_not_allow' ) {
1719
+ return false;
1720
+ }
1721
+
1722
+ if ( $this->user_cap_cache_enabled && isset($this->cached_user_caps[$capability]) ) {
1723
+ return $this->cached_user_caps[$capability];
1724
+ }
1725
+
1726
+ $user_can = apply_filters('admin_menu_editor-current_user_can', current_user_can($capability), $capability);
1727
+ $this->cached_user_caps[$capability] = $user_can;
1728
+ return $user_can;
1729
  }
1730
 
1731
  /**
1764
  $base_site_url = $matches[1];
1765
  }
1766
 
1767
+ //Calling admin_url() once and then manually appending each page's path is measurably faster than calling it
1768
+ //for each menu, but it means the "admin_url" filter is only called once. If there is a plugin that changes
1769
+ //the admin_url for some pages but not others, this could lead to bugs (no such plugins are known at this time).
1770
+ $base_admin_url = admin_url();
1771
+ $admin_url_is_filtered = has_filter('admin_url');
1772
+
1773
  $current_url = $base_site_url . remove_query_arg('___ame_dummy_param___');
1774
  $this->log_security_note(sprintf('Current URL: "%s"', htmlentities($current_url)));
1775
 
1782
  if ( substr($item_url, 0, 1) == '/' ) {
1783
  $item_url = $base_site_url . $item_url;
1784
  } else {
1785
+ if ( $admin_url_is_filtered ) {
1786
+ $item_url = admin_url($item_url);
1787
+ } else {
1788
+ $item_url = $base_admin_url . ltrim( $item_url, '/' );
1789
+ }
1790
  }
1791
  }
1792
  $item_url = $this->parse_url($item_url);
1909
 
1910
  $display_notice = $this->options['display_survey_notice'] && $this->current_user_can_edit_menu();
1911
  if ( isset($this->options['first_install_time']) ) {
1912
+ $minimum_usage_period = 7*24*3600;
1913
  $display_notice = $display_notice && ((time() - $this->options['first_install_time']) > $minimum_usage_period);
1914
  }
1915
 
1927
  $free_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dERyeDk0OWhlbkxYcEY4QTNaMnlTQUE6MQ';
1928
  $pro_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dHl4MnlHaVI3NE5JdVFDWG01SkRKTWc6MA';
1929
 
1930
+ if ( $this->is_pro_version() ) {
1931
  $survey_url = $pro_survey_url;
1932
  } else {
1933
  $survey_url = $free_survey_url;
2126
  return $name;
2127
  }
2128
 
2129
+ /**
2130
+ * Tell new users how to access the plugin settings page.
2131
+ */
2132
+ public function display_plugin_menu_notice() {
2133
+ //Display the notice only if it's enabled, the current user can access our settings page,
2134
+ //and there is no custom menu (if a custom menu already exists, chances are the user knows
2135
+ //where the settings page is).
2136
+ $showNotice = $this->options['show_plugin_menu_notice'] && ($this->load_custom_menu() === null);
2137
+ $showNotice = $showNotice && $this->current_user_can_edit_menu();
2138
+ if ( !$showNotice ) {
2139
+ return;
2140
+ }
2141
+
2142
+ //Disable the notice when the user hides it or visits any of our admin pages.
2143
+ $hideNoticeParameter = 'ame-plugin-menu-notice';
2144
+ if ( !empty($_GET[$hideNoticeParameter]) || $this->is_editor_page() || $this->is_settings_page() ) {
2145
+ $this->options['show_plugin_menu_notice'] = false;
2146
+ $this->save_options();
2147
+ return;
2148
+ }
2149
+
2150
+ $dismissUrl = add_query_arg($hideNoticeParameter, 'hide');
2151
+ $dismissUrl = remove_query_arg(array('message', 'activate'), $dismissUrl);
2152
+
2153
+ if ( is_multisite() && is_network_admin() ) {
2154
+ $message = 'Tip: Go to any subsite to access Admin Menu Editor. It will not show up in the network admin.';
2155
+ } else {
2156
+ $message = 'Tip: Go to <a href="%1$s">Settings -&gt; %2$s</a> to start customizing the admin menu.';
2157
+ }
2158
+ printf(
2159
+ '<div class="updated" id="ame-plugin-menu-notice">
2160
+ <p>' . $message . '</p>
2161
+ <p><a href="%3$s" id="ame-hide-plugin-menu-notice">Hide this message</a></p>
2162
+ </div>',
2163
+ esc_attr(admin_url($this->settings_link)),
2164
+ apply_filters('admin_menu_editor-self_menu_title', 'Menu Editor'),
2165
+ esc_attr($dismissUrl)
2166
+ );
2167
+
2168
+ }
2169
+
2170
+ private function is_pro_version() {
2171
+ return apply_filters('admin_menu_editor_is_pro', false);
2172
+ }
2173
+
2174
  } //class
includes/menu-item.php CHANGED
@@ -8,6 +8,22 @@
8
  * currently registered hooks and the presence of specific files in admin/plugin folders.
9
  */
10
  abstract class ameMenuItem {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  /**
12
  * Convert a WP menu structure to an associative array.
13
  *
@@ -193,6 +209,10 @@ abstract class ameMenuItem {
193
  }
194
  }
195
 
 
 
 
 
196
  return $parent_file . '>' . $item_file;
197
  }
198
 
@@ -218,10 +238,7 @@ abstract class ameMenuItem {
218
  /**
219
  * Apply custom menu filters to an item of the custom menu.
220
  *
221
- * Calls two types of filters :
222
- * 'custom_admin_$item_type' with the entire $item passed as the argument.
223
- * 'custom_admin_$item_type-$field' with the value of a single field of $item as the argument.
224
- *
225
  * Used when converting the current custom menu to a WP-format menu.
226
  *
227
  * @param array $item Associative array representing one menu item (either top-level or submenu).
@@ -230,12 +247,7 @@ abstract class ameMenuItem {
230
  * @return array Filtered menu item.
231
  */
232
  public static function apply_filters($item, $item_type, $extra = null){
233
- $item = apply_filters("custom_admin_{$item_type}", $item, $extra);
234
- foreach($item as $field => $value){
235
- $item[$field] = apply_filters("custom_admin_{$item_type}-$field", $value, $extra);
236
- }
237
-
238
- return $item;
239
  }
240
 
241
  /**
@@ -376,11 +388,28 @@ abstract class ameMenuItem {
376
  }
377
  $pageFile = self::remove_query_from($page_url);
378
 
379
- $hasHook = (get_plugin_page_hook($page_url, $parent_page_url) !== null);
 
 
 
 
 
380
  $adminFileExists = is_file(ABSPATH . '/wp-admin/' . $pageFile);
 
 
 
 
 
 
 
 
 
381
  $pluginFileExists = ($page_url != 'index.php') && is_file(WP_PLUGIN_DIR . '/' . $pageFile);
 
 
 
382
 
383
- return !$adminFileExists && ($hasHook || $pluginFileExists);
384
  }
385
 
386
  /**
8
  * currently registered hooks and the presence of specific files in admin/plugin folders.
9
  */
10
  abstract class ameMenuItem {
11
+ /**
12
+ * @var array A partial list of files in /wp-admin/. Correct as of WP 3.8-RC1, 2013.12.04.
13
+ * When trying to determine if a menu links to one of the default WP admin pages, it's faster
14
+ * to check this list than to hit the disk.
15
+ */
16
+ private static $known_wp_admin_files = array(
17
+ 'customize.php' => true, 'edit-comments.php' => true, 'edit-tags.php' => true, 'edit.php' => true,
18
+ 'export.php' => true, 'import.php' => true, 'index.php' => true, 'link-add.php' => true,
19
+ 'link-manager.php' => true, 'media-new.php' => true, 'nav-menus.php' => true, 'options-discussion.php' => true,
20
+ 'options-general.php' => true, 'options-media.php' => true, 'options-permalink.php' => true,
21
+ 'options-reading.php' => true, 'options-writing.php' => true, 'plugin-editor.php' => true,
22
+ 'plugin-install.php' => true, 'plugins.php' => true, 'post-new.php' => true, 'profile.php' => true,
23
+ 'theme-editor.php' => true, 'themes.php' => true, 'tools.php' => true, 'update-core.php' => true,
24
+ 'upload.php' => true, 'user-new.php' => true, 'users.php' => true, 'widgets.php' => true,
25
+ );
26
+
27
  /**
28
  * Convert a WP menu structure to an associative array.
29
  *
209
  }
210
  }
211
 
212
+ if ($parent_file === 'profile.php') {
213
+ $parent_file = 'users.php';
214
+ }
215
+
216
  return $parent_file . '>' . $item_file;
217
  }
218
 
238
  /**
239
  * Apply custom menu filters to an item of the custom menu.
240
  *
241
+ * Calls a 'custom_admin_$item_type' filter with the entire $item passed as the argument.
 
 
 
242
  * Used when converting the current custom menu to a WP-format menu.
243
  *
244
  * @param array $item Associative array representing one menu item (either top-level or submenu).
247
  * @return array Filtered menu item.
248
  */
249
  public static function apply_filters($item, $item_type, $extra = null){
250
+ return apply_filters("custom_admin_{$item_type}", $item, $extra);
 
 
 
 
 
251
  }
252
 
253
  /**
388
  }
389
  $pageFile = self::remove_query_from($page_url);
390
 
391
+ //Check our hard-coded list of admin pages first. It's measurably faster than
392
+ //hitting the disk with is_file().
393
+ if ( isset(self::$known_wp_admin_files[$pageFile]) ) {
394
+ return false;
395
+ }
396
+ //Now actually check the filesystem.
397
  $adminFileExists = is_file(ABSPATH . '/wp-admin/' . $pageFile);
398
+ if ( $adminFileExists ) {
399
+ return false;
400
+ }
401
+
402
+ $hasHook = (get_plugin_page_hook($page_url, $parent_page_url) !== null);
403
+ if ( $hasHook ) {
404
+ return true;
405
+ }
406
+
407
  $pluginFileExists = ($page_url != 'index.php') && is_file(WP_PLUGIN_DIR . '/' . $pageFile);
408
+ if ( $pluginFileExists ) {
409
+ return true;
410
+ }
411
 
412
+ return false;
413
  }
414
 
415
  /**
includes/menu.php CHANGED
@@ -1,42 +1,50 @@
1
  <?php
2
  abstract class ameMenu {
3
  const format_name = 'Admin Menu Editor menu';
4
- const format_version = '5.0';
5
 
6
  /**
7
  * Load an admin menu from a JSON string.
8
  *
9
  * @static
10
- * @throws InvalidMenuException when the supplied input is not a valid menu.
11
  *
12
  * @param string $json A JSON-encoded menu structure.
13
  * @param bool $assume_correct_format Skip the format header check and assume everything is fine. Defaults to false.
 
 
14
  * @return array
15
  */
16
- public static function load_json($json, $assume_correct_format = false) {
17
  $arr = json_decode($json, true);
18
  if ( !is_array($arr) ) {
19
  throw new InvalidMenuException('The input is not a valid JSON-encoded admin menu.');
20
  }
21
- return self::load_array($arr, $assume_correct_format);
22
  }
23
 
24
  /**
25
  * Load an admin menu structure from an associative array.
26
  *
27
  * @static
28
- * @throws InvalidMenuException when the supplied input is not a valid menu.
29
  *
30
  * @param array $arr
31
  * @param bool $assume_correct_format
 
 
32
  * @return array
33
  */
34
- public static function load_array($arr, $assume_correct_format = false){
 
35
  if ( !$assume_correct_format ) {
36
  if ( isset($arr['format']) && ($arr['format']['name'] == self::format_name) ) {
37
- if ( !version_compare($arr['format']['version'], self::format_version, '<=') ) {
 
38
  throw new InvalidMenuException("Can't load a menu created by a newer version of the plugin.");
39
  }
 
 
 
 
40
  } else {
41
  return self::load_menu_40($arr);
42
  }
@@ -49,8 +57,13 @@ abstract class ameMenu {
49
  $menu = array('tree' => array());
50
  $menu = self::add_format_header($menu);
51
 
52
- foreach($arr['tree'] as $file => $item) {
53
- $menu['tree'][$file] = ameMenuItem::normalize($item);
 
 
 
 
 
54
  }
55
 
56
  return $menu;
@@ -135,7 +148,7 @@ abstract class ameMenu {
135
  $parent = $tree_item['defaults']['file'];
136
  if ( isset($submenu[$parent]) ){
137
  foreach($submenu[$parent] as $position => $subitem){
138
- $tree_item['items'][$subitem[2]] = array_merge(
139
  ameMenuItem::blank_menu(),
140
  array('defaults' => ameMenuItem::fromWpItem($subitem, $position, $parent))
141
  );
@@ -149,6 +162,33 @@ abstract class ameMenu {
149
 
150
  return $tree;
151
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
 
154
 
1
  <?php
2
  abstract class ameMenu {
3
  const format_name = 'Admin Menu Editor menu';
4
+ const format_version = '5.1';
5
 
6
  /**
7
  * Load an admin menu from a JSON string.
8
  *
9
  * @static
 
10
  *
11
  * @param string $json A JSON-encoded menu structure.
12
  * @param bool $assume_correct_format Skip the format header check and assume everything is fine. Defaults to false.
13
+ * @param bool $always_normalize Always normalize the menu structure, even if format.is_normalized is true.
14
+ * @throws InvalidMenuException
15
  * @return array
16
  */
17
+ public static function load_json($json, $assume_correct_format = false, $always_normalize = false) {
18
  $arr = json_decode($json, true);
19
  if ( !is_array($arr) ) {
20
  throw new InvalidMenuException('The input is not a valid JSON-encoded admin menu.');
21
  }
22
+ return self::load_array($arr, $assume_correct_format, $always_normalize);
23
  }
24
 
25
  /**
26
  * Load an admin menu structure from an associative array.
27
  *
28
  * @static
 
29
  *
30
  * @param array $arr
31
  * @param bool $assume_correct_format
32
+ * @param bool $always_normalize
33
+ * @throws InvalidMenuException
34
  * @return array
35
  */
36
+ public static function load_array($arr, $assume_correct_format = false, $always_normalize = false){
37
+ $is_normalized = false;
38
  if ( !$assume_correct_format ) {
39
  if ( isset($arr['format']) && ($arr['format']['name'] == self::format_name) ) {
40
+ $compared = version_compare($arr['format']['version'], self::format_version);
41
+ if ( $compared > 0 ) {
42
  throw new InvalidMenuException("Can't load a menu created by a newer version of the plugin.");
43
  }
44
+ //We can skip normalization if the version number matches exactly and the menu is already normalized.
45
+ if ( ($compared === 0) && isset($arr['format']['is_normalized']) ) {
46
+ $is_normalized = $arr['format']['is_normalized'];
47
+ }
48
  } else {
49
  return self::load_menu_40($arr);
50
  }
57
  $menu = array('tree' => array());
58
  $menu = self::add_format_header($menu);
59
 
60
+ if ( $is_normalized && !$always_normalize ) {
61
+ $menu['tree'] = $arr['tree'];
62
+ } else {
63
+ foreach($arr['tree'] as $file => $item) {
64
+ $menu['tree'][$file] = ameMenuItem::normalize($item);
65
+ }
66
+ $menu['format']['is_normalized'] = true;
67
  }
68
 
69
  return $menu;
148
  $parent = $tree_item['defaults']['file'];
149
  if ( isset($submenu[$parent]) ){
150
  foreach($submenu[$parent] as $position => $subitem){
151
+ $tree_item['items'][] = array_merge(
152
  ameMenuItem::blank_menu(),
153
  array('defaults' => ameMenuItem::fromWpItem($subitem, $position, $parent))
154
  );
162
 
163
  return $tree;
164
  }
165
+
166
+ /**
167
+ * Check if a menu contains any items with the "hidden" flag set to true.
168
+ *
169
+ * @param array $menu
170
+ * @return bool
171
+ */
172
+ public static function has_hidden_items($menu) {
173
+ if ( !is_array($menu) || empty($menu) || empty($menu['tree']) ) {
174
+ return false;
175
+ }
176
+
177
+ foreach($menu['tree'] as $item) {
178
+ if ( ameMenuItem::get($item, 'hidden') ) {
179
+ return true;
180
+ }
181
+ if ( !empty($item['items']) ) {
182
+ foreach($item['items'] as $child) {
183
+ if ( ameMenuItem::get($child, 'hidden') ) {
184
+ return true;
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ return false;
191
+ }
192
  }
193
 
194
 
includes/settings-page.php CHANGED
@@ -125,11 +125,26 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
125
  <tr>
126
  <th scope="row">Interface</th>
127
  <td>
128
- <label>
129
- <input type="checkbox" name="hide_advanced_settings"
130
- <?php checked($this->options['hide_advanced_settings']); ?>>
131
- Hide advanced menu options by default
132
- </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  </td>
134
  </tr>
135
 
@@ -138,7 +153,7 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
138
  <td>
139
  <label>
140
  <input type="checkbox" name="security_logging_enabled"
141
- <?php checked($this->options['security_logging_enabled']); ?>>
142
  Show menu access checks performed by the plugin on every admin page
143
  </label>
144
  <br><span class="description">
125
  <tr>
126
  <th scope="row">Interface</th>
127
  <td>
128
+ <p>
129
+ <label>
130
+ <input type="checkbox" name="hide_advanced_settings"
131
+ <?php checked($settings['hide_advanced_settings']); ?>>
132
+ Hide advanced menu options by default
133
+ </label>
134
+ </p>
135
+
136
+ <?php if ($isProVersion): ?>
137
+ <p>
138
+ <label>
139
+ <input type="checkbox" name="show_deprecated_hide_button"
140
+ <?php checked($settings['show_deprecated_hide_button']); ?>>
141
+ Enable the "Show/Hide" toolbar button (not recommended)
142
+ </label>
143
+ <br><span class="description">
144
+ This feature is deprecated and is only kept for backwards compatibility purposes.
145
+ </span>
146
+ </p>
147
+ <?php endif; ?>
148
  </td>
149
  </tr>
150
 
153
  <td>
154
  <label>
155
  <input type="checkbox" name="security_logging_enabled"
156
+ <?php checked($settings['security_logging_enabled']); ?>>
157
  Show menu access checks performed by the plugin on every admin page
158
  </label>
159
  <br><span class="description">
js/menu-editor.js CHANGED
@@ -1358,7 +1358,18 @@ $(document).ready(function(){
1358
  var checked = $(this).is(':checked');
1359
  var containerNode = $(this).closest('.ws_container');
1360
 
1361
- setActorAccessForTreeAndUpdateUi(containerNode, selectedActor, checked);
 
 
 
 
 
 
 
 
 
 
 
1362
  });
1363
 
1364
  /**
@@ -1388,6 +1399,58 @@ $(document).ready(function(){
1388
  updateParentAccessUi(containerNode);
1389
  }
1390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1391
  /*************************************************************************
1392
  Access editor dialog
1393
  *************************************************************************/
@@ -1460,7 +1523,7 @@ $(document).ready(function(){
1460
  accessEditorState.menuItem = menuItem;
1461
 
1462
  //Show/hide the hint about sub menus overriding menu permissions.
1463
- var itemHasSubmenus = containerNode.data('submenu_id') &&
1464
  $('#' + containerNode.data('submenu_id')).find('.ws_item').length > 0;
1465
  var hintIsEnabled = !wsEditorData.showHints.hasOwnProperty('ws_hint_menu_permissions') || wsEditorData.showHints['ws_hint_menu_permissions'];
1466
  $('#ws_hint_menu_permissions').toggle(hintIsEnabled && itemHasSubmenus);
@@ -1479,7 +1542,8 @@ $(document).ready(function(){
1479
 
1480
  $('#ws_save_access_settings').click(function() {
1481
  //Save the new settings.
1482
- accessEditorState.menuItem.extra_capability = $('#ws_extra_capability').val();
 
1483
 
1484
  var grantAccess = accessEditorState.menuItem.grant_access;
1485
  if (!$.isPlainObject(grantAccess)) {
1358
  var checked = $(this).is(':checked');
1359
  var containerNode = $(this).closest('.ws_container');
1360
 
1361
+ var menu = containerNode.data('menu_item');
1362
+ //Ask for confirmation if the user tries to hide Dashboard -> Home.
1363
+ if ( !checked && ((menu.template_id == 'index.php>index.php') || (menu.template_id == '>index.php')) ) {
1364
+ updateItemEditor(containerNode); //Resets the checkbox back to the old value.
1365
+ confirmDashboardHiding(function(ok) {
1366
+ if (ok) {
1367
+ setActorAccessForTreeAndUpdateUi(containerNode, selectedActor, checked);
1368
+ }
1369
+ });
1370
+ } else {
1371
+ setActorAccessForTreeAndUpdateUi(containerNode, selectedActor, checked);
1372
+ }
1373
  });
1374
 
1375
  /**
1399
  updateParentAccessUi(containerNode);
1400
  }
1401
 
1402
+ /**
1403
+ * Confirm with the user that they want to hide "Dashboard -> Home".
1404
+ *
1405
+ * This particular menu is important because hiding it can cause an "insufficient permissions" error
1406
+ * to be displayed right when someone logs in, making it look like login failed.
1407
+ */
1408
+ var permissionConfirmationDialog = $('#ws-ame-dashboard-hide-confirmation').dialog({
1409
+ autoOpen: false,
1410
+ modal: true,
1411
+ closeText: ' ',
1412
+ width: 380,
1413
+ title: 'Warning'
1414
+ });
1415
+ var currentConfirmationCallback = function(ok) {};
1416
+
1417
+ /**
1418
+ * Confirm hiding "Dashboard -> Home".
1419
+ *
1420
+ * @param callback Called when the user selects an option. True = confirmed.
1421
+ */
1422
+ function confirmDashboardHiding(callback) {
1423
+ //The user can disable the confirmation dialog.
1424
+ if (!wsEditorData.dashboardHidingConfirmationEnabled) {
1425
+ callback(true);
1426
+ return;
1427
+ }
1428
+
1429
+ currentConfirmationCallback = callback;
1430
+ permissionConfirmationDialog.dialog('open');
1431
+ }
1432
+
1433
+ $('#ws_confirm_menu_hiding, #ws_cancel_menu_hiding').click(function() {
1434
+ var confirmed = $(this).is('#ws_confirm_menu_hiding');
1435
+ var dontShowAgain = permissionConfirmationDialog.find('.ws_dont_show_again input[type="checkbox"]').is(':checked');
1436
+
1437
+ currentConfirmationCallback(confirmed);
1438
+ permissionConfirmationDialog.dialog('close');
1439
+
1440
+ if (dontShowAgain) {
1441
+ wsEditorData.dashboardHidingConfirmationEnabled = false;
1442
+ //Run an AJAX request to disable the dialog for this user.
1443
+ $.post(
1444
+ wsEditorData.adminAjaxUrl,
1445
+ {
1446
+ 'action' : 'ws_ame_disable_dashboard_hiding_confirmation',
1447
+ '_ajax_nonce' : wsEditorData.disableDashboardConfirmationNonce
1448
+ }
1449
+ );
1450
+ }
1451
+ });
1452
+
1453
+
1454
  /*************************************************************************
1455
  Access editor dialog
1456
  *************************************************************************/
1523
  accessEditorState.menuItem = menuItem;
1524
 
1525
  //Show/hide the hint about sub menus overriding menu permissions.
1526
+ var itemHasSubmenus = !!(containerNode.data('submenu_id')) &&
1527
  $('#' + containerNode.data('submenu_id')).find('.ws_item').length > 0;
1528
  var hintIsEnabled = !wsEditorData.showHints.hasOwnProperty('ws_hint_menu_permissions') || wsEditorData.showHints['ws_hint_menu_permissions'];
1529
  $('#ws_hint_menu_permissions').toggle(hintIsEnabled && itemHasSubmenus);
1542
 
1543
  $('#ws_save_access_settings').click(function() {
1544
  //Save the new settings.
1545
+ var extraCapability = jsTrim($('#ws_extra_capability').val());
1546
+ accessEditorState.menuItem.extra_capability = (extraCapability === '') ? null : extraCapability;
1547
 
1548
  var grantAccess = accessEditorState.menuItem.grant_access;
1549
  if (!$.isPlainObject(grantAccess)) {
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.3
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.3.1
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: whiteshadow
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
- Requires at least: 3.2
6
- Tested up to: 3.6.1
7
- Stable tag: 1.3
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,17 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
 
 
 
 
 
66
  = 1.3 =
67
  * Added a new settings page that lets you choose whether admin menu settings are per-site or network-wide, as well as specify who can access the plugin. To access this page, go to "Settings -> Menu Editor Pro" and click the small "Settings" link next to the page title.
68
  * Added a way to show/hide advanced menu options through the settings page in addition to the "Screen Options" panel.
2
  Contributors: whiteshadow
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
+ Requires at least: 3.3
6
+ Tested up to: 3.8
7
+ Stable tag: 1.3.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.3.1 =
67
+ * Tested with WordPress 3.8.
68
+ * Fixed several minor UI/layout issues related to the new 3.8 admin style.
69
+ * Fixed a bug where moving an item to a plugin menu and then deactivating that plugin would cause the moved item to disappear.
70
+ * Fixed deleted submenus not being restored if their original parent menu is no longer available.
71
+ * Fixed a rare glitch where submenu separators added by certain other plugins would sometimes disappear.
72
+ * Fixed a conflict with Shopp 1.2.9.
73
+ * Made the plugin treat "users.php" and "profile.php" as the same parent menu. This fixes situations where it would be impossible to hide a "Users" submenu item from roles that don't have access to the "Users" menu and instead get a "Profile" menu.
74
+ * Added extra logging for situations where a menu item is hidden because a higher-priority item with the same URL is also hidden.
75
+ * Minor performance improvements.
76
+
77
  = 1.3 =
78
  * Added a new settings page that lets you choose whether admin menu settings are per-site or network-wide, as well as specify who can access the plugin. To access this page, go to "Settings -> Menu Editor Pro" and click the small "Settings" link next to the page title.
79
  * Added a way to show/hide advanced menu options through the settings page in addition to the "Screen Options" panel.