Admin Menu Editor - Version 1.8.2

Version Description

  • Fixed the PHP warning "count(): Parameter must be an array or an object that implements Countable in menu-editor-core.php".
  • Fixed a bug that could cause some network admin menus to be highlighted in green as if they were new.
  • Fixed a conflict with WP Courseware 4.1.2 where activating AME would cause many extra menu items to show up unexpectedly.
  • Fixed a conflict with Ultra WordPress Admin 7.4 that made it impossible to hide plugins.
  • Replaced the "this is a new item" icon with a different one.
  • Tested with WP 4.9.4.
Download this release

Release Info

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

Code changes from version 1.8.1 to 1.8.2

css/menu-editor.css CHANGED
@@ -422,7 +422,8 @@ to accommodate the drop-down button.
422
 
423
  /* unused items - those that are in the default menu but not in the custom one */
424
  .ws_unused_flag {
425
- background-image: url("../images/plugin_add.png"); }
 
426
 
427
  /* hidden items */
428
  .ws_hidden_flag {
@@ -446,6 +447,15 @@ to accommodate the drop-down button.
446
  .ws_hidden_from_others_flag {
447
  background-image: url("../images/font-awesome/eye-slash.png"); }
448
 
 
 
 
 
 
 
 
 
 
449
  /* These classes could be used to apply different styles to items depending on their flags */
450
  /************************************
451
  Toolbars
@@ -802,7 +812,8 @@ a#ws-ame-delete-color-preset:hover {
802
  font-size: 12px;
803
  border: 1px solid #ddd;
804
  background: white;
805
- cursor: pointer; }
 
806
 
807
  .ws_color_display_item {
808
  display: inline-block;
@@ -1320,6 +1331,8 @@ a#ws-ame-delete-color-preset:hover {
1320
 
1321
  .ws_ame_doc_box .hndle, .ws_ame_custom_postbox .hndle {
1322
  cursor: default !important; }
 
 
1323
  .ws_ame_doc_box ul, .ws_ame_custom_postbox ul {
1324
  list-style: disc outside;
1325
  margin-left: 1em; }
@@ -1344,6 +1357,19 @@ a#ws-ame-delete-color-preset:hover {
1344
  margin-left: 2px;
1345
  margin-right: 2px; }
1346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1347
  /************************************
1348
  Copy Permissions dialog
1349
  *************************************/
422
 
423
  /* unused items - those that are in the default menu but not in the custom one */
424
  .ws_unused_flag {
425
+ background-image: url("../images/new-menu-badge.png");
426
+ width: 31px; }
427
 
428
  /* hidden items */
429
  .ws_hidden_flag {
447
  .ws_hidden_from_others_flag {
448
  background-image: url("../images/font-awesome/eye-slash.png"); }
449
 
450
+ /* Item visibility can't be determined because it depends on a meta capability. */
451
+ .ws_uncertain_meta_cap_flag::before {
452
+ font: 16px/1 'dashicons';
453
+ content: "\f348";
454
+ color: black;
455
+ filter: alpha(opacity=25);
456
+ /*IE 5-7*/
457
+ opacity: 0.25; }
458
+
459
  /* These classes could be used to apply different styles to items depending on their flags */
460
  /************************************
461
  Toolbars
812
  font-size: 12px;
813
  border: 1px solid #ddd;
814
  background: white;
815
+ cursor: pointer;
816
+ line-height: 20px; }
817
 
818
  .ws_color_display_item {
819
  display: inline-block;
1331
 
1332
  .ws_ame_doc_box .hndle, .ws_ame_custom_postbox .hndle {
1333
  cursor: default !important; }
1334
+ .ws_ame_doc_box .inside, .ws_ame_custom_postbox .inside {
1335
+ margin-bottom: 0; }
1336
  .ws_ame_doc_box ul, .ws_ame_custom_postbox ul {
1337
  list-style: disc outside;
1338
  margin-left: 1em; }
1357
  margin-left: 2px;
1358
  margin-right: 2px; }
1359
 
1360
+ .ws_ame_custom_postbox .ame-tutorial-list {
1361
+ margin: 0; }
1362
+ .ws_ame_custom_postbox .ame-tutorial-list a {
1363
+ text-decoration: none;
1364
+ display: block;
1365
+ padding: 4px; }
1366
+ .ws_ame_custom_postbox .ame-tutorial-list ul {
1367
+ margin-left: 1em; }
1368
+ .ws_ame_custom_postbox .ame-tutorial-list li {
1369
+ display: block;
1370
+ margin: 0;
1371
+ list-style: none; }
1372
+
1373
  /************************************
1374
  Copy Permissions dialog
1375
  *************************************/
css/menu-editor.scss CHANGED
@@ -582,7 +582,8 @@ to accommodate the drop-down button.
582
 
583
  /* unused items - those that are in the default menu but not in the custom one */
584
  .ws_unused_flag {
585
- background-image: url('../images/plugin_add.png');
 
586
  }
587
 
588
  /* hidden items */
@@ -608,6 +609,16 @@ to accommodate the drop-down button.
608
  background-image: url('../images/font-awesome/eye-slash.png');
609
  }
610
 
 
 
 
 
 
 
 
 
 
 
611
  /* These classes could be used to apply different styles to items depending on their flags */
612
  .ws_custom { }
613
  .ws_hidden { }
@@ -1078,8 +1089,10 @@ a#ws-ame-delete-color-preset:hover {
1078
  /* Color scheme display in the editor widget. */
1079
 
1080
  .ws_color_scheme_display {
 
 
1081
  display: inline-block;
1082
- height: 20px;
1083
  width: 186px;
1084
 
1085
  margin-right: 5px;
@@ -1089,6 +1102,8 @@ a#ws-ame-delete-color-preset:hover {
1089
  border: 1px solid #ddd;
1090
  background: white;
1091
  cursor: pointer;
 
 
1092
  }
1093
 
1094
  .ws_color_display_item {
@@ -1860,6 +1875,10 @@ $userSelectionPanelPadding: 10px;
1860
  cursor: default !important;
1861
  }
1862
 
 
 
 
 
1863
  ul {
1864
  list-style: disc outside;
1865
  margin-left: 1em;
@@ -1896,6 +1915,26 @@ $userSelectionPanelPadding: 10px;
1896
  margin-right: 2px;
1897
  }
1898
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1899
  /************************************
1900
  Copy Permissions dialog
1901
  *************************************/
582
 
583
  /* unused items - those that are in the default menu but not in the custom one */
584
  .ws_unused_flag {
585
+ background-image: url('../images/new-menu-badge.png');
586
+ width: 31px;
587
  }
588
 
589
  /* hidden items */
609
  background-image: url('../images/font-awesome/eye-slash.png');
610
  }
611
 
612
+ /* Item visibility can't be determined because it depends on a meta capability. */
613
+ .ws_uncertain_meta_cap_flag::before {
614
+ font: 16px/1 'dashicons';
615
+ content: "\f348";
616
+ color: black;
617
+
618
+ filter: alpha(opacity=25); /*IE 5-7*/
619
+ opacity: 0.25;
620
+ }
621
+
622
  /* These classes could be used to apply different styles to items depending on their flags */
623
  .ws_custom { }
624
  .ws_hidden { }
1089
  /* Color scheme display in the editor widget. */
1090
 
1091
  .ws_color_scheme_display {
1092
+ $colorFieldHeight: 20px;
1093
+
1094
  display: inline-block;
1095
+ height: $colorFieldHeight;
1096
  width: 186px;
1097
 
1098
  margin-right: 5px;
1102
  border: 1px solid #ddd;
1103
  background: white;
1104
  cursor: pointer;
1105
+
1106
+ line-height: $colorFieldHeight;
1107
  }
1108
 
1109
  .ws_color_display_item {
1875
  cursor: default !important;
1876
  }
1877
 
1878
+ .inside {
1879
+ margin-bottom: 0;
1880
+ }
1881
+
1882
  ul {
1883
  list-style: disc outside;
1884
  margin-left: 1em;
1915
  margin-right: 2px;
1916
  }
1917
 
1918
+ .ws_ame_custom_postbox .ame-tutorial-list {
1919
+ margin: 0;
1920
+
1921
+ a {
1922
+ text-decoration: none;
1923
+ display: block;
1924
+ padding: 4px;
1925
+ }
1926
+
1927
+ ul {
1928
+ margin-left: 1em;
1929
+ }
1930
+
1931
+ li {
1932
+ display: block;
1933
+ margin: 0;
1934
+ list-style: none;
1935
+ }
1936
+ }
1937
+
1938
  /************************************
1939
  Copy Permissions dialog
1940
  *************************************/
images/new-menu-badge.png ADDED
Binary file
includes/editor-page.php CHANGED
@@ -286,6 +286,8 @@ function ame_output_sort_buttons($icons) {
286
  ?>
287
  </div>
288
 
 
 
289
  <?php
290
  if ( apply_filters('admin_menu_editor-show_general_box', false) ) :
291
  $is_general_box_open = true;
@@ -295,8 +297,6 @@ function ame_output_sort_buttons($icons) {
295
  $box_class = $is_general_box_open ? '' : 'closed';
296
 
297
  ?>
298
- <div class="clear"></div>
299
- <div class="metabox-holder">
300
  <div class="postbox ws_ame_custom_postbox <?php echo $box_class; ?>" id="ws_ame_general_vis_box">
301
  <button type="button" class="handlediv button-link">
302
  <span class="toggle-indicator"></span>
@@ -306,16 +306,73 @@ function ame_output_sort_buttons($icons) {
306
  <?php do_action('admin_menu_editor-general_box'); ?>
307
  </div>
308
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  </div>
310
  <?php
311
  endif;
 
 
312
 
 
313
  $hint_id = 'ws_sidebar_pro_ad';
314
  $show_pro_benefits = !apply_filters('admin_menu_editor_is_pro', false) && (!isset($editor_data['show_hints'][$hint_id]) || $editor_data['show_hints'][$hint_id]);
315
 
316
  if ( $show_pro_benefits ):
317
  $benefit_variations = array(
318
- 'Drag items between menu levels.',
319
  'More menu icons.',
320
  'Make menus open in a new tab or an iframe.',
321
  'Prevent users from deleting a specific user.',
286
  ?>
287
  </div>
288
 
289
+ <div class="clear"></div>
290
+ <div class="metabox-holder">
291
  <?php
292
  if ( apply_filters('admin_menu_editor-show_general_box', false) ) :
293
  $is_general_box_open = true;
297
  $box_class = $is_general_box_open ? '' : 'closed';
298
 
299
  ?>
 
 
300
  <div class="postbox ws_ame_custom_postbox <?php echo $box_class; ?>" id="ws_ame_general_vis_box">
301
  <button type="button" class="handlediv button-link">
302
  <span class="toggle-indicator"></span>
306
  <?php do_action('admin_menu_editor-general_box'); ?>
307
  </div>
308
  </div>
309
+ <?php
310
+ endif;
311
+
312
+ if ( $is_pro_version ) :
313
+ $is_how_to_box_open = true;
314
+ if ( isset($_COOKIE['ame_how_to_box_open']) ) {
315
+ $is_how_to_box_open = ($_COOKIE['ame_how_to_box_open'] === '1');
316
+ }
317
+ $box_class = $is_how_to_box_open ? '' : 'closed';
318
+
319
+ $how_to_link_template = '<a href="https://adminmenueditor.com/documentation/%1$s" target="_blank" title="Opens in a new tab">%2$s</a>';
320
+ $how_to_item_template = '<li>' . $how_to_link_template . '</li>';
321
+
322
+ ?>
323
+ <div class="postbox ws_ame_custom_postbox <?php echo $box_class; ?>" id="ws_ame_how_to_box">
324
+ <button type="button" class="handlediv button-link">
325
+ <span class="toggle-indicator"></span>
326
+ </button>
327
+ <h2 class="hndle">How To</h2>
328
+ <div class="inside">
329
+ <ul class="ame-tutorial-list">
330
+ <li><?php
331
+ printf($how_to_link_template, 'how-to-hide-a-menu-item/', 'Hide a Menu...');
332
+ ?>
333
+ <ul class="ame-tutorial-list">
334
+ <?php
335
+ foreach (
336
+ array(
337
+ 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-a-role' => 'From a Role',
338
+ 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-a-user' => 'From a User',
339
+ 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-everyone-except-yourself' => 'From Everyone Except You',
340
+ 'how-to-hide-menu-without-preventing-access/' => 'Without Preventing Access',
341
+ )
342
+ as $how_to_url => $how_to_title
343
+ ) {
344
+ printf($how_to_item_template, esc_attr($how_to_url), $how_to_title);
345
+ }
346
+ ?>
347
+ </ul>
348
+ </li>
349
+ <?php
350
+ foreach (
351
+ array(
352
+ 'how-to-give-access-to-menu/' => 'Show a Menu',
353
+ 'how-to-move-and-sort-menus/' => 'Move and Sort Menus',
354
+ 'how-to-add-a-new-menu-item/' => 'Add a New Menu',
355
+ )
356
+ as $how_to_url => $how_to_title
357
+ ) {
358
+ printf($how_to_item_template, esc_attr($how_to_url), $how_to_title);
359
+ }
360
+ ?>
361
+ </ul>
362
+ </div>
363
  </div>
364
  <?php
365
  endif;
366
+ ?>
367
+ </div> <!-- / .metabox-holder -->
368
 
369
+ <?php
370
  $hint_id = 'ws_sidebar_pro_ad';
371
  $show_pro_benefits = !apply_filters('admin_menu_editor_is_pro', false) && (!isset($editor_data['show_hints'][$hint_id]) || $editor_data['show_hints'][$hint_id]);
372
 
373
  if ( $show_pro_benefits ):
374
  $benefit_variations = array(
375
+ 'Hide dashboard widgets.',
376
  'More menu icons.',
377
  'Make menus open in a new tab or an iframe.',
378
  'Prevent users from deleting a specific user.',
includes/menu-editor-core.php CHANGED
@@ -114,6 +114,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
114
  */
115
  private $current_tab = '';
116
 
 
 
 
 
 
117
  function init(){
118
  $this->sitewide_options = true;
119
 
@@ -221,6 +226,22 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
221
  'index.php?page=nf-credits' => true,
222
  //All in One SEO Pack 2.3.9.2
223
  'index.php?page=aioseop-about' => true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  );
225
 
226
  //AJAXify screen options
@@ -320,6 +341,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
320
  add_action('admin_notices', array($this, 'display_security_log'));
321
  }
322
 
 
 
 
 
 
 
 
 
323
  //Modules
324
  foreach($this->get_active_modules() as $module) {
325
  /** @noinspection PhpIncludeInspection */
@@ -329,7 +358,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
329
  }
330
  }
331
 
332
- //Set up the tabs for the menu editor page.
333
  $firstTabs = array('editor' => 'Admin Menu');
334
  if ( is_network_admin() ) {
335
  //TODO: This could be in extras.php
@@ -676,20 +705,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
676
  }
677
  $done = true;
678
 
 
 
679
  //Lodash library
680
  wp_register_auto_versioned_script('ame-lodash', plugins_url('js/lodash.min.js', $this->plugin_file));
681
 
682
- //jQuery JSON plugin
683
- wp_register_auto_versioned_script('jquery-json', plugins_url('js/jquery.json.js', $this->plugin_file), array('jquery'));
684
- //jQuery sort plugin
685
- wp_register_auto_versioned_script('jquery-sort', plugins_url('js/jquery.sort.js', $this->plugin_file), array('jquery'));
686
- //qTip2 - jQuery tooltip plugin
687
- wp_register_auto_versioned_script('jquery-qtip', plugins_url('js/jquery.qtip.min.js', $this->plugin_file), array('jquery'));
688
- //jQuery Form plugin. This is a more recent version than the one included with WP.
689
- wp_register_auto_versioned_script('ame-jquery-form', plugins_url('js/jquery.form.js', $this->plugin_file), array('jquery'));
690
- //jQuery cookie plugin
691
- wp_register_auto_versioned_script('jquery-cookie', plugins_url('js/jquery.biscuit.js', $this->plugin_file), array('jquery'));
692
-
693
  //Knockout
694
  wp_register_auto_versioned_script('knockout', plugins_url('js/knockout.js', $this->plugin_file));
695
 
@@ -728,12 +748,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
728
  //Compatibility workaround: Get the real roles of the current user even if other plugins corrupt the list.
729
  $users[$current_user->get('user_login')]['roles'] = array_values($this->get_user_roles($current_user));
730
 
 
 
 
 
 
 
731
  //TODO: Include currentUserLogin
732
  $actor_data = array(
733
  'roles' => $roles,
734
  'users' => $users,
735
  'isMultisite' => is_multisite(),
736
  'capPower' => $this->load_cap_power(),
 
737
  );
738
  wp_localize_script('ame-actor-manager', 'wsAmeActorData', $actor_data);
739
 
@@ -748,6 +775,115 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
748
  do_action('admin_menu_editor-register_scripts');
749
  }
750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
 
752
  /**
753
  * Add the JS required by the editor to the page header
@@ -898,6 +1034,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
898
  'id' => $user->ID,
899
  'roles' => !empty($user->roles) ? (array)($user->roles) : array(),
900
  'capabilities' => $this->castValuesToBool($user->caps),
 
901
  'display_name' => $user->display_name,
902
  'is_super_admin' => is_multisite() && is_super_admin($user->ID),
903
  );
@@ -1185,7 +1322,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1185
  * @param string $login
1186
  * @param WP_User $current_user
1187
  */
1188
- public function maybe_reset_plugin_access(/** @noinspection PhpUnusedParameterInspection */ $login, $current_user) {
1189
  if ( ($this->options['plugin_access'] !== 'specific_user') || !$current_user || !$current_user->exists() ) {
1190
  return;
1191
  }
@@ -3849,7 +3986,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
3849
  continue;
3850
  }
3851
 
3852
- if ( count($line) >= 2 ) {
3853
  $cap_power[strval($line[0])] = floatval(str_replace(',', '.', $line[1]));
3854
  }
3855
  }
@@ -3938,6 +4075,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
3938
  }
3939
  unset($module);
3940
 
 
 
3941
  $modules = array_filter($modules, array($this, 'module_path_exists'));
3942
 
3943
  return $modules;
@@ -4037,6 +4176,7 @@ class ameMenuTemplateBuilder {
4037
  }
4038
 
4039
  //Skip blacklisted menus.
 
4040
  if ( isset($item['url'], $this->blacklist[$item['url']]) ) {
4041
  return;
4042
  }
114
  */
115
  private $current_tab = '';
116
 
117
+ /**
118
+ * @var array List of capabilities that are used in the default admin menu. Used to detect meta capabilities.
119
+ */
120
+ private $caps_used_in_menu = array();
121
+
122
  function init(){
123
  $this->sitewide_options = true;
124
 
226
  'index.php?page=nf-credits' => true,
227
  //All in One SEO Pack 2.3.9.2
228
  'index.php?page=aioseop-about' => true,
229
+ //WP Courseware 4.1.2
230
+ //'wpcw' => true, //This is commented out due to a bug. The Courseware top level menu and its first submenu
231
+ //both have the URL "wpcw", but the top level menu also has some visible, non-blacklisted items. AME would
232
+ //still hide the entire menu because the template builder doesn't check if a menu has submenu items.
233
+ 'admin.php?page=wpcw-course-classroom' => true,
234
+ 'admin.php?page=wpcw-student' => true,
235
+ 'admin.php?page=WPCW_showPage_ConvertPage' => true,
236
+ 'admin.php?page=WPCW_showPage_CourseOrdering' => true,
237
+ 'admin.php?page=WPCW_showPage_GradeBook' => true,
238
+ 'admin.php?page=WPCW_showPage_ModifyCourse' => true,
239
+ 'admin.php?page=WPCW_showPage_ModifyModule' => true,
240
+ 'admin.php?page=WPCW_showPage_ModifyQuestion' => true,
241
+ 'admin.php?page=WPCW_showPage_ModifyQuiz' => true,
242
+ 'admin.php?page=WPCW_showPage_UserCourseAccess' => true,
243
+ 'admin.php?page=WPCW_showPage_UserProgess' => true,
244
+ 'admin.php?page=WPCW_showPage_UserProgess_quizAnswers' => true,
245
  );
246
 
247
  //AJAXify screen options
341
  add_action('admin_notices', array($this, 'display_security_log'));
342
  }
343
 
344
+ if ( did_action('plugins_loaded') ) {
345
+ $this->load_modules();
346
+ } else {
347
+ add_action('plugins_loaded', array($this, 'load_modules'), 11);
348
+ }
349
+ }
350
+
351
+ public function load_modules() {
352
  //Modules
353
  foreach($this->get_active_modules() as $module) {
354
  /** @noinspection PhpIncludeInspection */
358
  }
359
  }
360
 
361
+ //Set up the tabs for the menu editor page. Many tabs are provided by modules.
362
  $firstTabs = array('editor' => 'Admin Menu');
363
  if ( is_network_admin() ) {
364
  //TODO: This could be in extras.php
705
  }
706
  $done = true;
707
 
708
+ $this->register_jquery_plugins();
709
+
710
  //Lodash library
711
  wp_register_auto_versioned_script('ame-lodash', plugins_url('js/lodash.min.js', $this->plugin_file));
712
 
 
 
 
 
 
 
 
 
 
 
 
713
  //Knockout
714
  wp_register_auto_versioned_script('knockout', plugins_url('js/knockout.js', $this->plugin_file));
715
 
748
  //Compatibility workaround: Get the real roles of the current user even if other plugins corrupt the list.
749
  $users[$current_user->get('user_login')]['roles'] = array_values($this->get_user_roles($current_user));
750
 
751
+ $suspected_meta_caps = $this->detect_meta_caps($roles, $users);
752
+
753
+ //The current user has all of the meta caps. That's how we know they're meta caps and not just regular
754
+ //capabilities that simply haven't been granted to anyone.
755
+ $users[$current_user->get('user_login')]['meta_capabilities'] = $suspected_meta_caps;
756
+
757
  //TODO: Include currentUserLogin
758
  $actor_data = array(
759
  'roles' => $roles,
760
  'users' => $users,
761
  'isMultisite' => is_multisite(),
762
  'capPower' => $this->load_cap_power(),
763
+ 'suspectedMetaCaps' => $suspected_meta_caps,
764
  );
765
  wp_localize_script('ame-actor-manager', 'wsAmeActorData', $actor_data);
766
 
775
  do_action('admin_menu_editor-register_scripts');
776
  }
777
 
778
+ /**
779
+ * @access private
780
+ */
781
+ public function register_jquery_plugins() {
782
+ //jQuery JSON plugin
783
+ wp_register_auto_versioned_script('jquery-json', plugins_url('js/jquery.json.js', $this->plugin_file), array('jquery'));
784
+ //jQuery sort plugin
785
+ wp_register_auto_versioned_script('jquery-sort', plugins_url('js/jquery.sort.js', $this->plugin_file), array('jquery'));
786
+ //qTip2 - jQuery tooltip plugin
787
+ wp_register_auto_versioned_script('jquery-qtip', plugins_url('js/jquery.qtip.min.js', $this->plugin_file), array('jquery'));
788
+ //jQuery Form plugin. This is a more recent version than the one included with WP.
789
+ wp_register_auto_versioned_script('ame-jquery-form', plugins_url('js/jquery.form.js', $this->plugin_file), array('jquery'));
790
+ //jQuery cookie plugin
791
+ wp_register_auto_versioned_script('jquery-cookie', plugins_url('js/jquery.biscuit.js', $this->plugin_file), array('jquery'));
792
+ }
793
+
794
+ /**
795
+ * Detect meta capabilities.
796
+ * This only works if the current user is an admin. In Multisite, they must be a Super Admin.
797
+ *
798
+ * @param array $roles
799
+ * @param array $users
800
+ * @return array [capability => true]
801
+ */
802
+ private function detect_meta_caps($roles, $users) {
803
+ if ( !$this->current_user_can_edit_menu() || !is_super_admin() ) {
804
+ return array();
805
+ }
806
+
807
+ //Any capability that's assigned to a role probably isn't a meta capability.
808
+ $allRealCaps = ameRoleUtils::get_all_capabilities();
809
+ //Similarly, capabilities that are directly assigned to users are probably real.
810
+ foreach($users as $user) {
811
+ $allRealCaps = array_merge($allRealCaps, $user['capabilities']);
812
+ }
813
+ //Role IDs can also be used as capabilities.
814
+ foreach($roles as $roleId => $role) {
815
+ $allRealCaps[$roleId] = true;
816
+ }
817
+
818
+ //Collect all of the required capabilities from the admin menu.
819
+ $menu = $this->get_default_menu();
820
+ ameMenu::for_each($menu['tree'], array($this, 'collect_menu_cap'));
821
+
822
+ //Any capability that's part of the admin menu but not assigned to any role or user
823
+ //is probably a meta capability.
824
+ $suspectedMetaCaps = array_diff_key($this->caps_used_in_menu, $allRealCaps);
825
+
826
+ //The current user is an admin and should have access to everything. If they don't have a cap,
827
+ //that's probably a non-meta cap that isn't enabled for *anyone*.
828
+ $suspectedMetaCaps = array_filter(array_keys($suspectedMetaCaps), 'current_user_can');
829
+
830
+ return array_fill_keys($suspectedMetaCaps, true);
831
+ }
832
+
833
+ /**
834
+ * @access private
835
+ * @param array $item
836
+ */
837
+ public function collect_menu_cap($item) {
838
+ if ( isset($item['defaults'], $item['defaults']['access_level']) ) {
839
+ $this->caps_used_in_menu[$item['defaults']['access_level']] = true;
840
+ }
841
+ }
842
+
843
+ /** @noinspection PhpUnusedPrivateMethodInspection */
844
+ /**
845
+ * Unfinished feature: Detect which roles have which meta capabilities.
846
+ *
847
+ * Create a temp. user for each role, test which meta caps they have, then cache the results in a site option.
848
+ * Put this part in an AJAX request to avoid a massive slowdown (takes several seconds even on a fast PC).
849
+ *
850
+ * @param array $suspected_meta_caps
851
+ * @param string[] $roleIds
852
+ * @return array
853
+ */
854
+ private function analyse_role_meta_caps($suspected_meta_caps, $roleIds) {
855
+ //$start = microtime(true);
856
+ $results = array();
857
+ $real_current_user = wp_get_current_user();
858
+
859
+ foreach($roleIds as $role_id) {
860
+ $id = wp_insert_user(array(
861
+ 'role' => $role_id,
862
+ 'user_login' => wp_slash('ametemp_' . wp_generate_password(14)),
863
+ 'user_pass' => wp_generate_password(20),
864
+ 'display_name' => 'Temporary user created by AME',
865
+ ));
866
+ $user = new WP_User($id);
867
+
868
+ //Some plugins only check the current user and ignore the user ID passed to the "user_has_cap" filter.
869
+ //To account for cases like that, we need to also change the current user.
870
+ wp_set_current_user($user->ID);
871
+
872
+ $results[$role_id] = array();
873
+ foreach($suspected_meta_caps as $meta_cap => $ignored) {
874
+ $results[$role_id][$meta_cap] = $user->has_cap($meta_cap);
875
+ }
876
+
877
+ wp_delete_user($id);
878
+ }
879
+
880
+ //Restore the original user.
881
+ wp_set_current_user($real_current_user->ID);
882
+
883
+ /*$elapsed = microtime(true) - $start;
884
+ printf('Meta cap analysis: %.2f ms<br>', $elapsed * 1000);*/
885
+ return $results;
886
+ }
887
 
888
  /**
889
  * Add the JS required by the editor to the page header
1034
  'id' => $user->ID,
1035
  'roles' => !empty($user->roles) ? (array)($user->roles) : array(),
1036
  'capabilities' => $this->castValuesToBool($user->caps),
1037
+ 'meta_capabilities' => array(),
1038
  'display_name' => $user->display_name,
1039
  'is_super_admin' => is_multisite() && is_super_admin($user->ID),
1040
  );
1322
  * @param string $login
1323
  * @param WP_User $current_user
1324
  */
1325
+ public function maybe_reset_plugin_access(/** @noinspection PhpUnusedParameterInspection */ $login = null, $current_user = null) {
1326
  if ( ($this->options['plugin_access'] !== 'specific_user') || !$current_user || !$current_user->exists() ) {
1327
  return;
1328
  }
3986
  continue;
3987
  }
3988
 
3989
+ if ( is_array($line) && (count($line) >= 2) ) {
3990
  $cap_power[strval($line[0])] = floatval(str_replace(',', '.', $line[1]));
3991
  }
3992
  }
4075
  }
4076
  unset($module);
4077
 
4078
+ $modules = apply_filters('admin_menu_editor-available_modules', $modules);
4079
+
4080
  $modules = array_filter($modules, array($this, 'module_path_exists'));
4081
 
4082
  return $modules;
4176
  }
4177
 
4178
  //Skip blacklisted menus.
4179
+ //BUG: We shouldn't skip top level menus that have non-blacklisted submenu items.
4180
  if ( isset($item['url'], $this->blacklist[$item['url']]) ) {
4181
  return;
4182
  }
includes/module.php CHANGED
@@ -7,6 +7,8 @@ abstract class ameModule {
7
  protected $moduleId = '';
8
  protected $moduleDir = '';
9
 
 
 
10
  /**
11
  * @var WPMenuEditor
12
  */
@@ -34,6 +36,11 @@ abstract class ameModule {
34
 
35
  add_action('admin_menu_editor-enqueue_scripts-' . $this->tabSlug, array($this, 'enqueueTabScripts'));
36
  add_action('admin_menu_editor-enqueue_styles-' . $this->tabSlug, array($this, 'enqueueTabStyles'));
 
 
 
 
 
37
  }
38
  }
39
 
@@ -80,6 +87,11 @@ abstract class ameModule {
80
  /** @noinspection PhpUnusedLocalVariableInspection Used in some templates. */
81
  $moduleTabUrl = $this->getTabUrl();
82
 
 
 
 
 
 
83
  /** @noinspection PhpIncludeInspection */
84
  require $templateFile;
85
  return true;
@@ -87,6 +99,11 @@ abstract class ameModule {
87
  return false;
88
  }
89
 
 
 
 
 
 
90
  public function registerScripts() {
91
  //Override this method to register scripts.
92
  }
@@ -98,4 +115,20 @@ abstract class ameModule {
98
  public function enqueueTabStyles() {
99
  //Override this method to add stylesheets to the $this->tabSlug tab.
100
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
7
  protected $moduleId = '';
8
  protected $moduleDir = '';
9
 
10
+ protected $settingsFormAction = '';
11
+
12
  /**
13
  * @var WPMenuEditor
14
  */
36
 
37
  add_action('admin_menu_editor-enqueue_scripts-' . $this->tabSlug, array($this, 'enqueueTabScripts'));
38
  add_action('admin_menu_editor-enqueue_styles-' . $this->tabSlug, array($this, 'enqueueTabStyles'));
39
+
40
+ //Optionally, handle settings form submission.
41
+ if ( $this->settingsFormAction !== '' ) {
42
+ add_action('admin_menu_editor-header', array($this, '_processAction'), 10, 2);
43
+ }
44
  }
45
  }
46
 
87
  /** @noinspection PhpUnusedLocalVariableInspection Used in some templates. */
88
  $moduleTabUrl = $this->getTabUrl();
89
 
90
+ $templateVariables = $this->getTemplateVariables($name);
91
+ if ( !empty($templateVariables) ) {
92
+ extract($templateVariables, EXTR_SKIP);
93
+ }
94
+
95
  /** @noinspection PhpIncludeInspection */
96
  require $templateFile;
97
  return true;
99
  return false;
100
  }
101
 
102
+ protected function getTemplateVariables(/** @noinspection PhpUnusedParameterInspection */ $templateName) {
103
+ //Override this method to pass variables to a template.
104
+ return array();
105
+ }
106
+
107
  public function registerScripts() {
108
  //Override this method to register scripts.
109
  }
115
  public function enqueueTabStyles() {
116
  //Override this method to add stylesheets to the $this->tabSlug tab.
117
  }
118
+
119
+ /**
120
+ * @access private
121
+ * @param string $action
122
+ * @param array $post
123
+ */
124
+ public function _processAction($action, $post = array()) {
125
+ if ( $action === $this->settingsFormAction ) {
126
+ check_admin_referer($action);
127
+ $this->handleSettingsForm($post);
128
+ }
129
+ }
130
+
131
+ public function handleSettingsForm($post = array()) {
132
+ //Override this method to process a form submitted from the module's tab.
133
+ }
134
  }
includes/role-utils.php CHANGED
@@ -1,11 +1,17 @@
1
  <?php
2
  class ameRoleUtils {
3
  /**
4
- * Retrieve a list of all known capabilities of all roles
5
  *
6
  * @return array Associative array with capability names as keys
7
  */
8
  public static function get_all_capabilities(){
 
 
 
 
 
 
9
  $wp_roles = self::get_roles();
10
  $capabilities = array();
11
 
1
  <?php
2
  class ameRoleUtils {
3
  /**
4
+ * Retrieve a list of all known, non-meta capabilities of all roles.
5
  *
6
  * @return array Associative array with capability names as keys
7
  */
8
  public static function get_all_capabilities(){
9
+ //Cache the results.
10
+ static $capabilities = null;
11
+ if ( isset($capabilities) ) {
12
+ return $capabilities;
13
+ }
14
+
15
  $wp_roles = self::get_roles();
16
  $capabilities = array();
17
 
js/actor-manager.js CHANGED
@@ -10,14 +10,15 @@ var __extends = (this && this.__extends) || (function () {
10
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
11
  };
12
  })();
13
- var AmeBaseActor = (function () {
14
- function AmeBaseActor(id, displayName, capabilities) {
 
15
  this.displayName = '[Error: No displayName set]';
16
  this.groupActors = [];
17
- this.actorTypeSpecificity = 0;
18
  this.id = id;
19
  this.displayName = displayName;
20
  this.capabilities = capabilities;
 
21
  }
22
  /**
23
  * Get the capability setting directly from this actor, ignoring capabilities
@@ -32,6 +33,9 @@ var AmeBaseActor = (function () {
32
  if (this.capabilities.hasOwnProperty(capability)) {
33
  return this.capabilities[capability];
34
  }
 
 
 
35
  return null;
36
  };
37
  AmeBaseActor.getActorSpecificity = function (actorId) {
@@ -56,11 +60,11 @@ var AmeBaseActor = (function () {
56
  };
57
  return AmeBaseActor;
58
  }());
59
- var AmeRole = (function (_super) {
60
  __extends(AmeRole, _super);
61
- function AmeRole(roleId, displayName, capabilities) {
62
- var _this = _super.call(this, 'role:' + roleId, displayName, capabilities) || this;
63
- _this.actorTypeSpecificity = 1;
64
  _this.name = roleId;
65
  return _this;
66
  }
@@ -75,15 +79,15 @@ var AmeRole = (function (_super) {
75
  };
76
  return AmeRole;
77
  }(AmeBaseActor));
78
- var AmeUser = (function (_super) {
79
  __extends(AmeUser, _super);
80
- function AmeUser(userLogin, displayName, capabilities, roles, isSuperAdmin, userId) {
81
  if (isSuperAdmin === void 0) { isSuperAdmin = false; }
82
- var _this = _super.call(this, 'user:' + userLogin, displayName, capabilities) || this;
 
83
  _this.userId = 0;
84
  _this.isSuperAdmin = false;
85
  _this.avatarHTML = '';
86
- _this.actorTypeSpecificity = 10;
87
  _this.userLogin = userLogin;
88
  _this.roles = roles;
89
  _this.isSuperAdmin = isSuperAdmin;
@@ -97,7 +101,7 @@ var AmeUser = (function (_super) {
97
  return _this;
98
  }
99
  AmeUser.createFromProperties = function (properties) {
100
- var user = new AmeUser(properties.user_login, properties.display_name, properties.capabilities, properties.roles, properties.is_super_admin, properties.hasOwnProperty('id') ? properties.id : null);
101
  if (properties.avatar_html) {
102
  user.avatarHTML = properties.avatar_html;
103
  }
@@ -105,12 +109,10 @@ var AmeUser = (function (_super) {
105
  };
106
  return AmeUser;
107
  }(AmeBaseActor));
108
- var AmeSuperAdmin = (function (_super) {
109
  __extends(AmeSuperAdmin, _super);
110
  function AmeSuperAdmin() {
111
- var _this = _super.call(this, AmeSuperAdmin.permanentActorId, 'Super Admin', {}) || this;
112
- _this.actorTypeSpecificity = 2;
113
- return _this;
114
  }
115
  AmeSuperAdmin.prototype.hasOwnCap = function (capability) {
116
  //The Super Admin has all possible capabilities except the special "do_not_allow" flag.
@@ -119,9 +121,10 @@ var AmeSuperAdmin = (function (_super) {
119
  AmeSuperAdmin.permanentActorId = 'special:super_admin';
120
  return AmeSuperAdmin;
121
  }(AmeBaseActor));
122
- var AmeActorManager = (function () {
123
- function AmeActorManager(roles, users, isMultisite) {
124
  if (isMultisite === void 0) { isMultisite = false; }
 
125
  var _this = this;
126
  this.roles = {};
127
  this.users = {};
@@ -132,7 +135,7 @@ var AmeActorManager = (function () {
132
  this.suggestedCapabilities = [];
133
  this.isMultisite = !!isMultisite;
134
  AmeActorManager._.forEach(roles, function (roleDetails, id) {
135
- var role = new AmeRole(id, roleDetails.name, roleDetails.capabilities);
136
  _this.roles[role.name] = role;
137
  });
138
  AmeActorManager._.forEach(users, function (userDetails) {
@@ -142,6 +145,7 @@ var AmeActorManager = (function () {
142
  if (this.isMultisite) {
143
  this.superAdmin = new AmeSuperAdmin();
144
  }
 
145
  var exclusiveCaps = [
146
  'update_core', 'update_plugins', 'delete_plugins', 'install_plugins', 'upload_plugins', 'update_themes',
147
  'delete_themes', 'install_themes', 'upload_themes', 'update_core', 'edit_css', 'unfiltered_html',
@@ -235,7 +239,11 @@ var AmeActorManager = (function () {
235
  }
236
  }
237
  //Step #3: Check owned/default capabilities. Always checked.
238
- var actor = this.getActor(actorId), hasOwnCap = actor.hasOwnCap(capability);
 
 
 
 
239
  if (hasOwnCap !== null) {
240
  return hasOwnCap;
241
  }
@@ -247,11 +255,19 @@ var AmeActorManager = (function () {
247
  return this.actorHasCap('special:super_admin', capability, contextList);
248
  }
249
  //Check if any of the user's roles have the capability.
250
- result = false;
251
  for (var index = 0; index < actor.roles.length; index++) {
252
- result = result || this.actorHasCap('role:' + actor.roles[index], capability, contextList);
 
 
 
253
  }
254
- return result;
 
 
 
 
 
255
  }
256
  return false;
257
  };
@@ -325,9 +341,6 @@ var AmeActorManager = (function () {
325
  var grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
326
  AmeActorManager._.set(context, [actor, capability], grant);
327
  };
328
- AmeActorManager.prototype.resetCap = function (actor, capability) {
329
- this.resetCapInContext(this.grantedCapabilities, actor, capability);
330
- };
331
  AmeActorManager.prototype.resetCapInContext = function (context, actor, capability) {
332
  capability = this.mapMetaCap(capability);
333
  if (AmeActorManager._.has(context, [actor, capability])) {
@@ -355,7 +368,7 @@ var AmeActorManager = (function () {
355
  _.forEach(_.keys(pruned[actor]), function (capability) {
356
  var grant = pruned[actor][capability];
357
  delete pruned[actor][capability];
358
- var hasCap = _.isArray(grant) ? grant[0] : grant, hasCapWhenPruned = _this.actorHasCap(actor, capability, context);
359
  if (hasCap !== hasCapWhenPruned) {
360
  pruned[actor][capability] = grant; //Restore.
361
  }
@@ -471,7 +484,7 @@ var AmeActorManager = (function () {
471
  return AmeActorManager;
472
  }());
473
  if (typeof wsAmeActorData !== 'undefined') {
474
- AmeActors = new AmeActorManager(wsAmeActorData.roles, wsAmeActorData.users, wsAmeActorData.isMultisite);
475
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
476
  AmeActors.generateCapabilitySuggestions(wsAmeActorData['capPower']);
477
  }
10
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
11
  };
12
  })();
13
+ var AmeBaseActor = /** @class */ (function () {
14
+ function AmeBaseActor(id, displayName, capabilities, metaCapabilities) {
15
+ if (metaCapabilities === void 0) { metaCapabilities = {}; }
16
  this.displayName = '[Error: No displayName set]';
17
  this.groupActors = [];
 
18
  this.id = id;
19
  this.displayName = displayName;
20
  this.capabilities = capabilities;
21
+ this.metaCapabilities = metaCapabilities;
22
  }
23
  /**
24
  * Get the capability setting directly from this actor, ignoring capabilities
33
  if (this.capabilities.hasOwnProperty(capability)) {
34
  return this.capabilities[capability];
35
  }
36
+ if (this.metaCapabilities.hasOwnProperty(capability)) {
37
+ return this.metaCapabilities[capability];
38
+ }
39
  return null;
40
  };
41
  AmeBaseActor.getActorSpecificity = function (actorId) {
60
  };
61
  return AmeBaseActor;
62
  }());
63
+ var AmeRole = /** @class */ (function (_super) {
64
  __extends(AmeRole, _super);
65
+ function AmeRole(roleId, displayName, capabilities, metaCapabilities) {
66
+ if (metaCapabilities === void 0) { metaCapabilities = {}; }
67
+ var _this = _super.call(this, 'role:' + roleId, displayName, capabilities, metaCapabilities) || this;
68
  _this.name = roleId;
69
  return _this;
70
  }
79
  };
80
  return AmeRole;
81
  }(AmeBaseActor));
82
+ var AmeUser = /** @class */ (function (_super) {
83
  __extends(AmeUser, _super);
84
+ function AmeUser(userLogin, displayName, capabilities, roles, isSuperAdmin, userId, metaCapabilities) {
85
  if (isSuperAdmin === void 0) { isSuperAdmin = false; }
86
+ if (metaCapabilities === void 0) { metaCapabilities = {}; }
87
+ var _this = _super.call(this, 'user:' + userLogin, displayName, capabilities, metaCapabilities) || this;
88
  _this.userId = 0;
89
  _this.isSuperAdmin = false;
90
  _this.avatarHTML = '';
 
91
  _this.userLogin = userLogin;
92
  _this.roles = roles;
93
  _this.isSuperAdmin = isSuperAdmin;
101
  return _this;
102
  }
103
  AmeUser.createFromProperties = function (properties) {
104
+ var user = new AmeUser(properties.user_login, properties.display_name, properties.capabilities, properties.roles, properties.is_super_admin, properties.hasOwnProperty('id') ? properties.id : null, properties.meta_capabilities);
105
  if (properties.avatar_html) {
106
  user.avatarHTML = properties.avatar_html;
107
  }
109
  };
110
  return AmeUser;
111
  }(AmeBaseActor));
112
+ var AmeSuperAdmin = /** @class */ (function (_super) {
113
  __extends(AmeSuperAdmin, _super);
114
  function AmeSuperAdmin() {
115
+ return _super.call(this, AmeSuperAdmin.permanentActorId, 'Super Admin', {}) || this;
 
 
116
  }
117
  AmeSuperAdmin.prototype.hasOwnCap = function (capability) {
118
  //The Super Admin has all possible capabilities except the special "do_not_allow" flag.
121
  AmeSuperAdmin.permanentActorId = 'special:super_admin';
122
  return AmeSuperAdmin;
123
  }(AmeBaseActor));
124
+ var AmeActorManager = /** @class */ (function () {
125
+ function AmeActorManager(roles, users, isMultisite, suspectedMetaCaps) {
126
  if (isMultisite === void 0) { isMultisite = false; }
127
+ if (suspectedMetaCaps === void 0) { suspectedMetaCaps = {}; }
128
  var _this = this;
129
  this.roles = {};
130
  this.users = {};
135
  this.suggestedCapabilities = [];
136
  this.isMultisite = !!isMultisite;
137
  AmeActorManager._.forEach(roles, function (roleDetails, id) {
138
+ var role = new AmeRole(id, roleDetails.name, roleDetails.capabilities, AmeActorManager._.get(roleDetails, 'meta_capabilities', {}));
139
  _this.roles[role.name] = role;
140
  });
141
  AmeActorManager._.forEach(users, function (userDetails) {
145
  if (this.isMultisite) {
146
  this.superAdmin = new AmeSuperAdmin();
147
  }
148
+ this.suspectedMetaCaps = suspectedMetaCaps;
149
  var exclusiveCaps = [
150
  'update_core', 'update_plugins', 'delete_plugins', 'install_plugins', 'upload_plugins', 'update_themes',
151
  'delete_themes', 'install_themes', 'upload_themes', 'update_core', 'edit_css', 'unfiltered_html',
239
  }
240
  }
241
  //Step #3: Check owned/default capabilities. Always checked.
242
+ var actor = this.getActor(actorId);
243
+ if (actor === null) {
244
+ return false;
245
+ }
246
+ var hasOwnCap = actor.hasOwnCap(capability);
247
  if (hasOwnCap !== null) {
248
  return hasOwnCap;
249
  }
255
  return this.actorHasCap('special:super_admin', capability, contextList);
256
  }
257
  //Check if any of the user's roles have the capability.
258
+ result = null;
259
  for (var index = 0; index < actor.roles.length; index++) {
260
+ var roleHasCap = this.actorHasCap('role:' + actor.roles[index], capability, contextList);
261
+ if (roleHasCap !== null) {
262
+ result = result || roleHasCap;
263
+ }
264
  }
265
+ if (result !== null) {
266
+ return result;
267
+ }
268
+ }
269
+ if (this.suspectedMetaCaps.hasOwnProperty(capability)) {
270
+ return null;
271
  }
272
  return false;
273
  };
341
  var grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
342
  AmeActorManager._.set(context, [actor, capability], grant);
343
  };
 
 
 
344
  AmeActorManager.prototype.resetCapInContext = function (context, actor, capability) {
345
  capability = this.mapMetaCap(capability);
346
  if (AmeActorManager._.has(context, [actor, capability])) {
368
  _.forEach(_.keys(pruned[actor]), function (capability) {
369
  var grant = pruned[actor][capability];
370
  delete pruned[actor][capability];
371
+ var hasCap = _.isArray(grant) ? grant[0] : grant, hasCapWhenPruned = !!_this.actorHasCap(actor, capability, context);
372
  if (hasCap !== hasCapWhenPruned) {
373
  pruned[actor][capability] = grant; //Restore.
374
  }
484
  return AmeActorManager;
485
  }());
486
  if (typeof wsAmeActorData !== 'undefined') {
487
+ AmeActors = new AmeActorManager(wsAmeActorData.roles, wsAmeActorData.users, wsAmeActorData.isMultisite, wsAmeActorData.suspectedMetaCaps);
488
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
489
  AmeActors.generateCapabilitySuggestions(wsAmeActorData['capPower']);
490
  }
js/actor-manager.ts CHANGED
@@ -5,6 +5,9 @@ declare let wsAmeActorData: any;
5
  declare var wsAmeLodash: _.LoDashStatic;
6
  declare let AmeActors: AmeActorManager;
7
 
 
 
 
8
  interface CapabilityMap {
9
  [capabilityName: string] : boolean;
10
  }
@@ -13,15 +16,15 @@ abstract class AmeBaseActor {
13
  public id: string;
14
  public displayName: string = '[Error: No displayName set]';
15
  public capabilities: CapabilityMap;
 
16
 
17
  groupActors: string[] = [];
18
 
19
- protected actorTypeSpecificity: Number = 0;
20
-
21
- constructor(id: string, displayName: string, capabilities: CapabilityMap) {
22
  this.id = id;
23
  this.displayName = displayName;
24
  this.capabilities = capabilities;
 
25
  }
26
 
27
  /**
@@ -37,6 +40,9 @@ abstract class AmeBaseActor {
37
  if (this.capabilities.hasOwnProperty(capability)) {
38
  return this.capabilities[capability];
39
  }
 
 
 
40
  return null;
41
  }
42
 
@@ -66,10 +72,9 @@ abstract class AmeBaseActor {
66
 
67
  class AmeRole extends AmeBaseActor {
68
  name: string;
69
- protected actorTypeSpecificity = 1;
70
 
71
- constructor(roleId: string, displayName: string, capabilities: CapabilityMap) {
72
- super('role:' + roleId, displayName, capabilities);
73
  this.name = roleId;
74
  }
75
 
@@ -88,6 +93,7 @@ interface AmeUserPropertyMap {
88
  user_login: string;
89
  display_name: string;
90
  capabilities: CapabilityMap;
 
91
  roles : string[];
92
  is_super_admin: boolean;
93
  id?: number;
@@ -102,17 +108,16 @@ class AmeUser extends AmeBaseActor {
102
  groupActors: string[];
103
  avatarHTML: string = '';
104
 
105
- protected actorTypeSpecificity = 10;
106
-
107
  constructor(
108
  userLogin: string,
109
  displayName: string,
110
  capabilities: CapabilityMap,
111
  roles: string[],
112
  isSuperAdmin: boolean = false,
113
- userId?: number
 
114
  ) {
115
- super('user:' + userLogin, displayName, capabilities);
116
 
117
  this.userLogin = userLogin;
118
  this.roles = roles;
@@ -134,7 +139,8 @@ class AmeUser extends AmeBaseActor {
134
  properties.capabilities,
135
  properties.roles,
136
  properties.is_super_admin,
137
- properties.hasOwnProperty('id') ? properties.id : null
 
138
  );
139
 
140
  if (properties.avatar_html) {
@@ -147,7 +153,6 @@ class AmeUser extends AmeBaseActor {
147
 
148
  class AmeSuperAdmin extends AmeBaseActor {
149
  static permanentActorId = 'special:super_admin';
150
- protected actorTypeSpecificity = 2;
151
 
152
  constructor() {
153
  super(AmeSuperAdmin.permanentActorId, 'Super Admin', {});
@@ -182,14 +187,20 @@ class AmeActorManager {
182
  private exclusiveSuperAdminCapabilities = {};
183
 
184
  private tagMetaCaps = {};
 
185
 
186
  private suggestedCapabilities: AmeCapabilitySuggestion[] = [];
187
 
188
- constructor(roles, users, isMultisite: boolean = false) {
189
  this.isMultisite = !!isMultisite;
190
 
191
  AmeActorManager._.forEach(roles, (roleDetails, id) => {
192
- const role = new AmeRole(id, roleDetails.name, roleDetails.capabilities);
 
 
 
 
 
193
  this.roles[role.name] = role;
194
  });
195
 
@@ -202,6 +213,8 @@ class AmeActorManager {
202
  this.superAdmin = new AmeSuperAdmin();
203
  }
204
 
 
 
205
  const exclusiveCaps: string[] = [
206
  'update_core', 'update_plugins', 'delete_plugins', 'install_plugins', 'upload_plugins', 'update_themes',
207
  'delete_themes', 'install_themes', 'upload_themes', 'update_core', 'edit_css', 'unfiltered_html',
@@ -277,7 +290,7 @@ class AmeActorManager {
277
  return this.actorHasCap(actorId, capability);
278
  }
279
 
280
- private actorHasCap(actorId: string, capability: string, contextList?: Array<Object>): boolean {
281
  //It's like the chain-of-responsibility pattern.
282
 
283
  //Everybody has the "exist" cap and it can't be removed or overridden by plugins.
@@ -310,8 +323,11 @@ class AmeActorManager {
310
  }
311
 
312
  //Step #3: Check owned/default capabilities. Always checked.
313
- let actor = this.getActor(actorId),
314
- hasOwnCap = actor.hasOwnCap(capability);
 
 
 
315
  if (hasOwnCap !== null) {
316
  return hasOwnCap;
317
  }
@@ -325,13 +341,21 @@ class AmeActorManager {
325
  }
326
 
327
  //Check if any of the user's roles have the capability.
328
- result = false;
329
  for (let index = 0; index < actor.roles.length; index++) {
330
- result = result || this.actorHasCap('role:' + actor.roles[index], capability, contextList);
 
 
 
 
 
 
331
  }
332
- return result;
333
  }
334
 
 
 
 
335
  return false;
336
  }
337
 
@@ -425,10 +449,6 @@ class AmeActorManager {
425
  AmeActorManager._.set(context, [actor, capability], grant);
426
  }
427
 
428
- resetCap(actor: string, capability: string) {
429
- this.resetCapInContext(this.grantedCapabilities, actor, capability);
430
- }
431
-
432
  public resetCapInContext(context: AmeGrantedCapabilityMap, actor: string, capability: string) {
433
  capability = this.mapMetaCap(capability);
434
 
@@ -463,7 +483,7 @@ class AmeActorManager {
463
  delete pruned[actor][capability];
464
 
465
  const hasCap = _.isArray(grant) ? grant[0] : grant,
466
- hasCapWhenPruned = this.actorHasCap(actor, capability, context);
467
 
468
  if (hasCap !== hasCapWhenPruned) {
469
  pruned[actor][capability] = grant; //Restore.
@@ -605,7 +625,8 @@ if (typeof wsAmeActorData !== 'undefined') {
605
  AmeActors = new AmeActorManager(
606
  wsAmeActorData.roles,
607
  wsAmeActorData.users,
608
- wsAmeActorData.isMultisite
 
609
  );
610
 
611
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
5
  declare var wsAmeLodash: _.LoDashStatic;
6
  declare let AmeActors: AmeActorManager;
7
 
8
+ type Falsy = false | null | '' | undefined | 0;
9
+ type Truthy = true | string | 1;
10
+
11
  interface CapabilityMap {
12
  [capabilityName: string] : boolean;
13
  }
16
  public id: string;
17
  public displayName: string = '[Error: No displayName set]';
18
  public capabilities: CapabilityMap;
19
+ public metaCapabilities: CapabilityMap;
20
 
21
  groupActors: string[] = [];
22
 
23
+ constructor(id: string, displayName: string, capabilities: CapabilityMap, metaCapabilities: CapabilityMap = {}) {
 
 
24
  this.id = id;
25
  this.displayName = displayName;
26
  this.capabilities = capabilities;
27
+ this.metaCapabilities = metaCapabilities;
28
  }
29
 
30
  /**
40
  if (this.capabilities.hasOwnProperty(capability)) {
41
  return this.capabilities[capability];
42
  }
43
+ if (this.metaCapabilities.hasOwnProperty(capability)) {
44
+ return this.metaCapabilities[capability];
45
+ }
46
  return null;
47
  }
48
 
72
 
73
  class AmeRole extends AmeBaseActor {
74
  name: string;
 
75
 
76
+ constructor(roleId: string, displayName: string, capabilities: CapabilityMap, metaCapabilities: CapabilityMap = {}) {
77
+ super('role:' + roleId, displayName, capabilities, metaCapabilities);
78
  this.name = roleId;
79
  }
80
 
93
  user_login: string;
94
  display_name: string;
95
  capabilities: CapabilityMap;
96
+ meta_capabilities: CapabilityMap;
97
  roles : string[];
98
  is_super_admin: boolean;
99
  id?: number;
108
  groupActors: string[];
109
  avatarHTML: string = '';
110
 
 
 
111
  constructor(
112
  userLogin: string,
113
  displayName: string,
114
  capabilities: CapabilityMap,
115
  roles: string[],
116
  isSuperAdmin: boolean = false,
117
+ userId?: number,
118
+ metaCapabilities: CapabilityMap = {}
119
  ) {
120
+ super('user:' + userLogin, displayName, capabilities, metaCapabilities);
121
 
122
  this.userLogin = userLogin;
123
  this.roles = roles;
139
  properties.capabilities,
140
  properties.roles,
141
  properties.is_super_admin,
142
+ properties.hasOwnProperty('id') ? properties.id : null,
143
+ properties.meta_capabilities
144
  );
145
 
146
  if (properties.avatar_html) {
153
 
154
  class AmeSuperAdmin extends AmeBaseActor {
155
  static permanentActorId = 'special:super_admin';
 
156
 
157
  constructor() {
158
  super(AmeSuperAdmin.permanentActorId, 'Super Admin', {});
187
  private exclusiveSuperAdminCapabilities = {};
188
 
189
  private tagMetaCaps = {};
190
+ private suspectedMetaCaps: CapabilityMap;
191
 
192
  private suggestedCapabilities: AmeCapabilitySuggestion[] = [];
193
 
194
+ constructor(roles, users, isMultisite: Truthy | Falsy = false, suspectedMetaCaps: CapabilityMap = {}) {
195
  this.isMultisite = !!isMultisite;
196
 
197
  AmeActorManager._.forEach(roles, (roleDetails, id) => {
198
+ const role = new AmeRole(
199
+ id,
200
+ roleDetails.name,
201
+ roleDetails.capabilities,
202
+ AmeActorManager._.get(roleDetails, 'meta_capabilities', {})
203
+ );
204
  this.roles[role.name] = role;
205
  });
206
 
213
  this.superAdmin = new AmeSuperAdmin();
214
  }
215
 
216
+ this.suspectedMetaCaps = suspectedMetaCaps;
217
+
218
  const exclusiveCaps: string[] = [
219
  'update_core', 'update_plugins', 'delete_plugins', 'install_plugins', 'upload_plugins', 'update_themes',
220
  'delete_themes', 'install_themes', 'upload_themes', 'update_core', 'edit_css', 'unfiltered_html',
290
  return this.actorHasCap(actorId, capability);
291
  }
292
 
293
+ private actorHasCap(actorId: string, capability: string, contextList?: Array<Object>): (boolean | null) {
294
  //It's like the chain-of-responsibility pattern.
295
 
296
  //Everybody has the "exist" cap and it can't be removed or overridden by plugins.
323
  }
324
 
325
  //Step #3: Check owned/default capabilities. Always checked.
326
+ let actor = this.getActor(actorId);
327
+ if (actor === null) {
328
+ return false;
329
+ }
330
+ let hasOwnCap = actor.hasOwnCap(capability);
331
  if (hasOwnCap !== null) {
332
  return hasOwnCap;
333
  }
341
  }
342
 
343
  //Check if any of the user's roles have the capability.
344
+ result = null;
345
  for (let index = 0; index < actor.roles.length; index++) {
346
+ let roleHasCap = this.actorHasCap('role:' + actor.roles[index], capability, contextList);
347
+ if (roleHasCap !== null) {
348
+ result = result || roleHasCap;
349
+ }
350
+ }
351
+ if (result !== null) {
352
+ return result;
353
  }
 
354
  }
355
 
356
+ if (this.suspectedMetaCaps.hasOwnProperty(capability)) {
357
+ return null;
358
+ }
359
  return false;
360
  }
361
 
449
  AmeActorManager._.set(context, [actor, capability], grant);
450
  }
451
 
 
 
 
 
452
  public resetCapInContext(context: AmeGrantedCapabilityMap, actor: string, capability: string) {
453
  capability = this.mapMetaCap(capability);
454
 
483
  delete pruned[actor][capability];
484
 
485
  const hasCap = _.isArray(grant) ? grant[0] : grant,
486
+ hasCapWhenPruned = !!this.actorHasCap(actor, capability, context);
487
 
488
  if (hasCap !== hasCapWhenPruned) {
489
  pruned[actor][capability] = grant; //Restore.
625
  AmeActors = new AmeActorManager(
626
  wsAmeActorData.roles,
627
  wsAmeActorData.users,
628
+ wsAmeActorData.isMultisite,
629
+ wsAmeActorData.suspectedMetaCaps
630
  );
631
 
632
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
js/menu-editor.js CHANGED
@@ -1191,13 +1191,13 @@ function updateActorAccessUi(containerNode) {
1191
  var checkbox = containerNode.find('.ws_actor_access_checkbox');
1192
  checkbox.prop('checked', hasAccess);
1193
 
1194
- //Display the checkbox differently if some items of this menu are hidden and some are visible,
 
 
 
1195
  //or if their permissions don't match this menu's permissions.
1196
  var submenuItems = getSubmenuItemNodes(containerNode);
1197
- if ((submenuItems.length === 0) || isOverrideActive) {
1198
- //Either this menu doesn't contain any items, or their permissions don't matter because they're overridden.
1199
- checkbox.prop('indeterminate', false);
1200
- } else {
1201
  var differentPermissions = false;
1202
  submenuItems.each(function() {
1203
  var item = $(this).data('menu_item');
@@ -1212,7 +1212,24 @@ function updateActorAccessUi(containerNode) {
1212
  return true;
1213
  });
1214
 
1215
- checkbox.prop('indeterminate', differentPermissions);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1216
  }
1217
 
1218
  containerNode.toggleClass('ws_is_hidden_for_actor', !hasAccess);
@@ -1222,12 +1239,17 @@ function updateActorAccessUi(containerNode) {
1222
  } else {
1223
  containerNode.removeClass('ws_is_hidden_for_actor ws_has_custom_permissions_for_actor');
1224
  setMenuFlag(containerNode, 'custom_actor_permissions', false);
 
1225
 
1226
  var currentUserActor = 'user:' + wsEditorData.currentUserLogin;
1227
  var otherActors = _(wsEditorData.actors).keys().without(currentUserActor, 'special:super_admin').value(),
1228
  hiddenFromCurrentUser = ! actorCanAccessMenu(menuItem, currentUserActor),
1229
- hiddenFromOthers = ! _.some(otherActors, _.curry(actorCanAccessMenu, 2)(menuItem)),
 
 
 
1230
  visibleForSuperAdmin = AmeActors.isMultisite && actorCanAccessMenu(menuItem, 'special:super_admin');
 
1231
  setMenuFlag(
1232
  containerNode,
1233
  'hidden_from_others',
@@ -1729,11 +1751,12 @@ function readAllFields(container){
1729
  ***************************************************************************/
1730
 
1731
  var item_flags = {
1732
- 'custom':'This is a custom menu item',
1733
- 'unused':'This item was automatically recreated. You cannot delete a non-custom item, but you could hide it.',
1734
- 'hidden':'Cosmetically hidden',
1735
- 'custom_actor_permissions' : "The selected role has custom permissions for this item.",
1736
- 'hidden_from_others' : 'Hidden from everyone except you.'
 
1737
  };
1738
 
1739
  function setMenuFlag(item, flag, state, title) {
@@ -4729,6 +4752,17 @@ function ameOnDomReady() {
4729
  );
4730
  });
4731
 
 
 
 
 
 
 
 
 
 
 
 
4732
 
4733
  /******************************************************************
4734
  Actor views
1191
  var checkbox = containerNode.find('.ws_actor_access_checkbox');
1192
  checkbox.prop('checked', hasAccess);
1193
 
1194
+ //Display the checkbox in an indeterminate state if the actual menu permissions are unknown
1195
+ //because it uses meta capabilities.
1196
+ var isIndeterminate = (hasAccess === null);
1197
+ //Also show it as indeterminate if some items of this menu are hidden and some are visible,
1198
  //or if their permissions don't match this menu's permissions.
1199
  var submenuItems = getSubmenuItemNodes(containerNode);
1200
+ if ((submenuItems.length > 0) && !isOverrideActive) {
 
 
 
1201
  var differentPermissions = false;
1202
  submenuItems.each(function() {
1203
  var item = $(this).data('menu_item');
1212
  return true;
1213
  });
1214
 
1215
+ if (differentPermissions) {
1216
+ isIndeterminate = true;
1217
+ }
1218
+ }
1219
+ checkbox.prop('indeterminate', isIndeterminate);
1220
+
1221
+ if (isIndeterminate && (hasAccess === null)) {
1222
+ setMenuFlag(
1223
+ containerNode,
1224
+ 'uncertain_meta_cap',
1225
+ true,
1226
+ "This item might be visible.\n"
1227
+ + "The plugin cannot reliably detect if \"" + actorSelectorWidget.selectedDisplayName
1228
+ + "\" has the \"" + getFieldValue(menuItem, 'access_level', '[No capability]')
1229
+ + "\" capability. If you need to hide the item, try checking and then unchecking it."
1230
+ );
1231
+ } else {
1232
+ setMenuFlag(containerNode, 'uncertain_meta_cap', false);
1233
  }
1234
 
1235
  containerNode.toggleClass('ws_is_hidden_for_actor', !hasAccess);
1239
  } else {
1240
  containerNode.removeClass('ws_is_hidden_for_actor ws_has_custom_permissions_for_actor');
1241
  setMenuFlag(containerNode, 'custom_actor_permissions', false);
1242
+ setMenuFlag(containerNode, 'uncertain_meta_cap', false);
1243
 
1244
  var currentUserActor = 'user:' + wsEditorData.currentUserLogin;
1245
  var otherActors = _(wsEditorData.actors).keys().without(currentUserActor, 'special:super_admin').value(),
1246
  hiddenFromCurrentUser = ! actorCanAccessMenu(menuItem, currentUserActor),
1247
+ hasAccessToThisItem = _.curry(actorCanAccessMenu, 2)(menuItem),
1248
+ hiddenFromOthers = _.every(otherActors, function(actorId) {
1249
+ return (hasAccessToThisItem(actorId) === false);
1250
+ }),
1251
  visibleForSuperAdmin = AmeActors.isMultisite && actorCanAccessMenu(menuItem, 'special:super_admin');
1252
+
1253
  setMenuFlag(
1254
  containerNode,
1255
  'hidden_from_others',
1751
  ***************************************************************************/
1752
 
1753
  var item_flags = {
1754
+ 'custom': 'This is a custom menu item',
1755
+ 'unused': 'This item was added since the last time you saved menu settings.',
1756
+ 'hidden': 'Cosmetically hidden',
1757
+ 'custom_actor_permissions': "The selected role has custom permissions for this item.",
1758
+ 'hidden_from_others': 'Hidden from everyone except you.',
1759
+ 'uncertain_meta_cap': 'The plugin cannot detect if this item is visible by default.'
1760
  };
1761
 
1762
  function setMenuFlag(item, flag, state, title) {
4752
  );
4753
  });
4754
 
4755
+ //Expand/collapse the "How To" box.
4756
+ var $howToBox = $("#ws_ame_how_to_box");
4757
+ $howToBox.find(".handlediv").click(function() {
4758
+ $howToBox.toggleClass('closed');
4759
+ $.cookie(
4760
+ 'ame_how_to_box_open',
4761
+ ($howToBox.hasClass('closed') ? '0' : '1'),
4762
+ { expires: 180 }
4763
+ );
4764
+ });
4765
+
4766
 
4767
  /******************************************************************
4768
  Actor views
menu-editor.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
- Version: 1.8.1
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
+ Version: 1.8.2
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
modules/actor-selector/actor-selector.js CHANGED
@@ -1,10 +1,11 @@
1
  /// <reference path="../../js/jquery.d.ts" />
2
  /// <reference path="../../js/jquery-json.d.ts" />
3
  /// <reference path="../../js/actor-manager.ts" />
4
- var AmeActorSelector = (function () {
5
  function AmeActorSelector(actorManager, isProVersion, currentUserLogin, visibleUsers, ajaxParams) {
6
  var _this = this;
7
  this.selectedActor = null;
 
8
  this.visibleUsers = [];
9
  this.subscribers = [];
10
  this.isProVersion = false;
@@ -88,6 +89,12 @@ var AmeActorSelector = (function () {
88
  var previousSelection = this.selectedActor;
89
  this.selectedActor = actorId;
90
  this.highlightSelectedActor();
 
 
 
 
 
 
91
  //Notify subscribers that the selection has changed.
92
  if (this.selectedActor !== previousSelection) {
93
  for (var i = 0; i < this.subscribers.length; i++) {
@@ -199,3 +206,4 @@ var AmeActorSelector = (function () {
199
  AmeActorSelector._ = wsAmeLodash;
200
  return AmeActorSelector;
201
  }());
 
1
  /// <reference path="../../js/jquery.d.ts" />
2
  /// <reference path="../../js/jquery-json.d.ts" />
3
  /// <reference path="../../js/actor-manager.ts" />
4
+ var AmeActorSelector = /** @class */ (function () {
5
  function AmeActorSelector(actorManager, isProVersion, currentUserLogin, visibleUsers, ajaxParams) {
6
  var _this = this;
7
  this.selectedActor = null;
8
+ this.selectedDisplayName = 'All';
9
  this.visibleUsers = [];
10
  this.subscribers = [];
11
  this.isProVersion = false;
89
  var previousSelection = this.selectedActor;
90
  this.selectedActor = actorId;
91
  this.highlightSelectedActor();
92
+ if (actorId !== null) {
93
+ this.selectedDisplayName = this.actorManager.getActor(actorId).displayName;
94
+ }
95
+ else {
96
+ this.selectedDisplayName = 'All';
97
+ }
98
  //Notify subscribers that the selection has changed.
99
  if (this.selectedActor !== previousSelection) {
100
  for (var i = 0; i < this.subscribers.length; i++) {
206
  AmeActorSelector._ = wsAmeLodash;
207
  return AmeActorSelector;
208
  }());
209
+ //# sourceMappingURL=actor-selector.js.map
modules/actor-selector/actor-selector.ts CHANGED
@@ -27,6 +27,7 @@ class AmeActorSelector {
27
  private static _ = wsAmeLodash;
28
 
29
  public selectedActor: string = null;
 
30
 
31
  private visibleUsers: string[] = [];
32
  private subscribers: SelectedActorChangedCallback[] = [];
@@ -128,6 +129,12 @@ class AmeActorSelector {
128
  this.selectedActor = actorId;
129
  this.highlightSelectedActor();
130
 
 
 
 
 
 
 
131
  //Notify subscribers that the selection has changed.
132
  if (this.selectedActor !== previousSelection) {
133
  for (let i = 0; i < this.subscribers.length; i++) {
27
  private static _ = wsAmeLodash;
28
 
29
  public selectedActor: string = null;
30
+ public selectedDisplayName: string = 'All';
31
 
32
  private visibleUsers: string[] = [];
33
  private subscribers: SelectedActorChangedCallback[] = [];
129
  this.selectedActor = actorId;
130
  this.highlightSelectedActor();
131
 
132
+ if (actorId !== null) {
133
+ this.selectedDisplayName = this.actorManager.getActor(actorId).displayName;
134
+ } else {
135
+ this.selectedDisplayName = 'All';
136
+ }
137
+
138
  //Notify subscribers that the selection has changed.
139
  if (this.selectedActor !== previousSelection) {
140
  for (let i = 0; i < this.subscribers.length; i++) {
modules/highlight-new-menus/wsNewMenuHighlighter.php CHANGED
@@ -44,6 +44,12 @@ class wsNewMenuHighlighter {
44
  'user-new.php' => true,
45
  'users.php' => true,
46
  'widgets.php' => true,
 
 
 
 
 
 
47
  );
48
 
49
  private $menusWithNewSubmenus = array();
@@ -180,10 +186,10 @@ class wsNewMenuHighlighter {
180
 
181
  if ( isset($GLOBALS['wp_menu_editor']) && is_callable(array(
182
  $GLOBALS['wp_menu_editor'],
183
- 'register_base_dependencies',
184
  ))
185
  ) {
186
- $GLOBALS['wp_menu_editor']->register_base_dependencies();
187
  $dependencies[] = 'jquery-cookie';
188
  }
189
 
44
  'user-new.php' => true,
45
  'users.php' => true,
46
  'widgets.php' => true,
47
+ //Network admin items.
48
+ 'settings.php' => true,
49
+ 'site-new.php' => true,
50
+ 'sites.php' => true,
51
+ 'theme-install.php' => true,
52
+ 'upgrade.php' => true,
53
  );
54
 
55
  private $menusWithNewSubmenus = array();
186
 
187
  if ( isset($GLOBALS['wp_menu_editor']) && is_callable(array(
188
  $GLOBALS['wp_menu_editor'],
189
+ 'register_jquery_plugins',
190
  ))
191
  ) {
192
+ $GLOBALS['wp_menu_editor']->register_jquery_plugins();
193
  $dependencies[] = 'jquery-cookie';
194
  }
195
 
modules/plugin-visibility/plugin-visibility.js CHANGED
@@ -4,7 +4,7 @@
4
  /// <reference path="../../js/lodash-3.10.d.ts" />
5
  /// <reference path="../../modules/actor-selector/actor-selector.ts" />
6
  /// <reference path="../../ajax-wrapper/ajax-action-wrapper.d.ts" />
7
- var AmePluginVisibilityModule = (function () {
8
  function AmePluginVisibilityModule(scriptData) {
9
  var _this = this;
10
  var _ = AmePluginVisibilityModule._;
@@ -159,7 +159,7 @@ var AmePluginVisibilityModule = (function () {
159
  AmePluginVisibilityModule._ = wsAmeLodash;
160
  return AmePluginVisibilityModule;
161
  }());
162
- var AmePlugin = (function () {
163
  function AmePlugin(details, settings, module) {
164
  var _this = this;
165
  var _ = AmePluginVisibilityModule._;
4
  /// <reference path="../../js/lodash-3.10.d.ts" />
5
  /// <reference path="../../modules/actor-selector/actor-selector.ts" />
6
  /// <reference path="../../ajax-wrapper/ajax-action-wrapper.d.ts" />
7
+ var AmePluginVisibilityModule = /** @class */ (function () {
8
  function AmePluginVisibilityModule(scriptData) {
9
  var _this = this;
10
  var _ = AmePluginVisibilityModule._;
159
  AmePluginVisibilityModule._ = wsAmeLodash;
160
  return AmePluginVisibilityModule;
161
  }());
162
+ var AmePlugin = /** @class */ (function () {
163
  function AmePlugin(details, settings, module) {
164
  var _this = this;
165
  var _ = AmePluginVisibilityModule._;
modules/plugin-visibility/plugin-visibility.php CHANGED
@@ -20,7 +20,7 @@ class amePluginVisibility {
20
  self::$lastInstance = $this;
21
 
22
  //Remove "hidden" plugins from the list on the "Plugins -> Installed Plugins" page.
23
- add_filter('all_plugins', array($this, 'filterPluginList'));
24
 
25
  //It's not possible to completely prevent a user from (de)activating "hidden" plugins because plugin API
26
  //functions like activate_plugin() and deactivate_plugins() don't provide a way to abort (de)activation.
20
  self::$lastInstance = $this;
21
 
22
  //Remove "hidden" plugins from the list on the "Plugins -> Installed Plugins" page.
23
+ add_filter('all_plugins', array($this, 'filterPluginList'), 15);
24
 
25
  //It's not possible to completely prevent a user from (de)activating "hidden" plugins because plugin API
26
  //functions like activate_plugin() and deactivate_plugins() don't provide a way to abort (de)activation.
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.9
7
- Stable tag: 1.8.1
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
@@ -63,6 +63,14 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
 
 
66
  = 1.8.1 =
67
  * Added a workaround for a buggy "defer_parsing_of_js" code snippet that some users have added to their functions.php. This snippet produces invalid HTML code, which used to break the menu editor.
68
  * Fixed a PHP warning that appeared when using this plugin together with WooCommerce or YITH WooCommerce Gift Cards and running PHP 7.1.
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
  Requires at least: 4.1
6
+ Tested up to: 4.9.4
7
+ Stable tag: 1.8.2
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
63
 
64
  == Changelog ==
65
 
66
+ = 1.8.2 =
67
+ * Fixed the PHP warning "count(): Parameter must be an array or an object that implements Countable in menu-editor-core.php".
68
+ * Fixed a bug that could cause some network admin menus to be highlighted in green as if they were new.
69
+ * Fixed a conflict with WP Courseware 4.1.2 where activating AME would cause many extra menu items to show up unexpectedly.
70
+ * Fixed a conflict with Ultra WordPress Admin 7.4 that made it impossible to hide plugins.
71
+ * Replaced the "this is a new item" icon with a different one.
72
+ * Tested with WP 4.9.4.
73
+
74
  = 1.8.1 =
75
  * Added a workaround for a buggy "defer_parsing_of_js" code snippet that some users have added to their functions.php. This snippet produces invalid HTML code, which used to break the menu editor.
76
  * Fixed a PHP warning that appeared when using this plugin together with WooCommerce or YITH WooCommerce Gift Cards and running PHP 7.1.