Admin Menu Editor - Version 1.2

Version Description

  • Added an icon drop-down that lets you pick one of the default WordPress menu icons or upload your own through the media library (only in WP 3.5+).
  • Fixed misaligned button text in IE/Firefox.
  • Fixed menus that have both a custom icon URL and a "menu-icon-*" class displaying two overlapping icons. You can still get this effect if you set the class and URL manually.
  • Fixed a compatibility problem with Participants Database 1.4.5.2.
  • Tested on WP 3.5.1 and WP 3.6-alpha.
Download this release

Release Info

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

Code changes from version 1.1.13 to 1.2

css/menu-editor.css CHANGED
@@ -40,7 +40,6 @@
40
  display: block;
41
  margin: 4px;
42
  width: 120px;
43
- padding: 4px;
44
  }
45
 
46
  #ws_editor_sidebar #ws_save_menu {
@@ -245,7 +244,7 @@ a.ws_edit_link:hover {
245
  float: left;
246
 
247
  width: 20px;
248
- height: 20px;
249
 
250
  margin: 1px 1px 1px 0;
251
  padding: 0;
@@ -431,6 +430,108 @@ a.ws_button:hover {
431
  padding-left: 10px;
432
  }
433
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  /************************************
435
  Export and import
436
  *************************************/
40
  display: block;
41
  margin: 4px;
42
  width: 120px;
 
43
  }
44
 
45
  #ws_editor_sidebar #ws_save_menu {
244
  float: left;
245
 
246
  width: 20px;
247
+ height: 22px;
248
 
249
  margin: 1px 1px 1px 0;
250
  padding: 0;
430
  padding-left: 10px;
431
  }
432
 
433
+ /************************************
434
+ Icon selector
435
+ *************************************/
436
+
437
+ #ws_icon_selector {
438
+ border: 1px solid silver;
439
+ border-radius: 3px;
440
+ background-color: white;
441
+ width: 144px;
442
+ padding: 2px;
443
+ }
444
+
445
+ #ws_icon_selector .ws_icon_option {
446
+ float: left;
447
+ height: 30px;
448
+
449
+ margin: 2px;
450
+ cursor: pointer;
451
+ border: 1px solid #bbb;
452
+ border-radius: 3px;
453
+
454
+ /* Gradients and colours cribbed from WP 3.5.1 button styles */
455
+ background: #f3f3f3;
456
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#f4f4f4));
457
+ background-image: -webkit-linear-gradient(top, #fefefe, #f4f4f4);
458
+ background-image: -moz-linear-gradient(top, #fefefe, #f4f4f4);
459
+ background-image: -o-linear-gradient(top, #fefefe, #f4f4f4);
460
+ background-image: linear-gradient(to bottom, #fefefe, #f4f4f4);
461
+ }
462
+
463
+ #ws_icon_selector .ws_icon_option:hover {
464
+ /* Gradients and colours cribbed from WP 3.5.1 button styles */
465
+ border-color: #999;
466
+ background: #f3f3f3;
467
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f3f3f3));
468
+ background-image: -webkit-linear-gradient(top, #fff, #f3f3f3);
469
+ background-image: -moz-linear-gradient(top, #fff, #f3f3f3);
470
+ background-image: -ms-linear-gradient(top, #fff, #f3f3f3);
471
+ background-image: -o-linear-gradient(top, #fff, #f3f3f3);
472
+ background-image: linear-gradient(to bottom, #fff, #f3f3f3);
473
+ }
474
+
475
+ #ws_icon_selector .ws_icon_option.ws_selected_icon {
476
+ border-color: green;
477
+ background-color: #deffca;
478
+ background-image: none;
479
+ }
480
+
481
+ #ws_icon_selector .icon16 {
482
+ float: none;
483
+ margin: 0;
484
+ }
485
+
486
+ #ws_icon_selector .ws_icon_option img {
487
+ display: inline-block;
488
+ margin: 0;
489
+ padding: 7px;
490
+
491
+ width: 16px;
492
+ height: 16px;
493
+ }
494
+
495
+ #ws_menu_editor .ws_edit_field-icon_url input.ws_field_value {
496
+ width: 220px;
497
+ margin-right: 5px;
498
+ }
499
+
500
+ /* The icon button that displays the pop-up icon selector. */
501
+ #ws_menu_editor .ws_select_icon {
502
+ margin: 0;
503
+ padding: 0;
504
+ position: relative;
505
+ }
506
+
507
+ /* Current icon node (CSS class version, for the built-in WP icon sprites) */
508
+ .ws_select_icon .icon16 {
509
+ margin: 0;
510
+ float: none;
511
+ padding: 3px;
512
+
513
+ /*
514
+ The default .icon16 style has a 6px padding which would normally make it too large
515
+ to fit in the button. We can't change the padding without making the background-position
516
+ look wrong, so lets offset the icon so that it fits.
517
+ */
518
+ position: relative;
519
+ top: -3px;
520
+ left: -3px;
521
+ }
522
+
523
+ /* Current icon node (image version) */
524
+ .ws_select_icon img {
525
+ margin: 0;
526
+ padding: 4px;
527
+ width: 16px;
528
+ height: 16px;
529
+ }
530
+
531
+ #ws_choose_icon_from_media {
532
+ margin: 2px;
533
+ }
534
+
535
  /************************************
536
  Export and import
537
  *************************************/
includes/menu-editor-core.php CHANGED
@@ -9,7 +9,9 @@ if (class_exists('WPMenuEditor')){
9
  }
10
 
11
  //Load the "framework"
12
- require 'shadow_plugin_framework.php';
 
 
13
 
14
  if ( !class_exists('WPMenuEditor') ) :
15
 
@@ -167,13 +169,18 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
167
  wp_enqueue_script('jquery-sort', plugins_url('js/jquery.sort.js', $this->plugin_file), array('jquery'));
168
  //jQuery UI Droppable
169
  wp_enqueue_script('jquery-ui-droppable');
 
 
 
 
 
170
 
171
  //Editor's scipts
172
  wp_enqueue_script(
173
  'menu-editor',
174
  plugins_url('js/menu-editor.js', $this->plugin_file),
175
  array('jquery', 'jquery-ui-sortable', 'jquery-ui-dialog', 'jquery-form'),
176
- '20120915'
177
  );
178
 
179
  //The editor will need access to some of the plugin data and WP data.
@@ -187,13 +194,28 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
187
  );
188
  }
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  /**
191
  * Add the editor's CSS file to the page header
192
  *
193
  * @return void
194
  */
195
  function enqueue_styles(){
196
- wp_enqueue_style('menu-editor-style', plugins_url('css/menu-editor.css', $this->plugin_file), array(), '20121210');
197
  }
198
 
199
  /**
@@ -225,6 +247,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
225
  add_action("admin_print_scripts-$page", array(&$this, 'enqueue_scripts'));
226
  add_action("admin_print_styles-$page", array(&$this, 'enqueue_styles'));
227
 
 
 
 
228
  //Make a placeholder for our screen options (hacky)
229
  add_meta_box("ws-ame-screen-options", "You should never see this", array(&$this, 'noop'), $page);
230
  }
@@ -796,11 +821,24 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
796
  if ( !empty($topmenu['separator']) && !$first_nonseparator_found ) continue;
797
 
798
  $first_nonseparator_found = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
799
 
800
  //Apply defaults & filters
801
  $topmenu = $this->apply_defaults($topmenu);
802
  $topmenu = $this->apply_menu_filters($topmenu, 'menu');
803
-
804
  //Skip hidden entries
805
  if (!empty($topmenu['hidden'])) continue;
806
 
@@ -1161,6 +1199,55 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1161
  echo implode("\n", $pageSelector);
1162
  ?>
1163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1164
  <span id="ws-ame-screen-meta-contents" style="display:none;">
1165
  <label for="ws-hide-advanced-settings">
1166
  <input type="checkbox" id="ws-hide-advanced-settings"<?php
9
  }
10
 
11
  //Load the "framework"
12
+ $thisDirectory = dirname(__FILE__);
13
+ require $thisDirectory . '/shadow_plugin_framework.php';
14
+ require $thisDirectory . '/menu-item.php';
15
 
16
  if ( !class_exists('WPMenuEditor') ) :
17
 
169
  wp_enqueue_script('jquery-sort', plugins_url('js/jquery.sort.js', $this->plugin_file), array('jquery'));
170
  //jQuery UI Droppable
171
  wp_enqueue_script('jquery-ui-droppable');
172
+
173
+ //We use WordPress media uploader to let the user upload custom menu icons (WP 3.5+).
174
+ if ( function_exists('wp_enqueue_media') ) {
175
+ wp_enqueue_media();
176
+ }
177
 
178
  //Editor's scipts
179
  wp_enqueue_script(
180
  'menu-editor',
181
  plugins_url('js/menu-editor.js', $this->plugin_file),
182
  array('jquery', 'jquery-ui-sortable', 'jquery-ui-dialog', 'jquery-form'),
183
+ '20130221'
184
  );
185
 
186
  //The editor will need access to some of the plugin data and WP data.
194
  );
195
  }
196
 
197
+ /**
198
+ * Compatibility workaround for Participants Database 1.4.5.2.
199
+ *
200
+ * Participants Database loads its settings JavaScript on every page in the "Settings" menu,
201
+ * not just its own. It doesn't bother to also load the script's dependencies, though, so
202
+ * the script crashes *and* it breaks the menu editor by way of collateral damage.
203
+ *
204
+ * Fix by forcibly removing the offending script from the queue.
205
+ */
206
+ public function dequeue_pd_scripts() {
207
+ if ( is_plugin_active('participants-database/participants-database.php') ) {
208
+ wp_dequeue_script('settings_script');
209
+ }
210
+ }
211
+
212
  /**
213
  * Add the editor's CSS file to the page header
214
  *
215
  * @return void
216
  */
217
  function enqueue_styles(){
218
+ wp_enqueue_style('menu-editor-style', plugins_url('css/menu-editor.css', $this->plugin_file), array(), '20130221');
219
  }
220
 
221
  /**
247
  add_action("admin_print_scripts-$page", array(&$this, 'enqueue_scripts'));
248
  add_action("admin_print_styles-$page", array(&$this, 'enqueue_styles'));
249
 
250
+ //Compatibility fix for Participants Database.
251
+ add_action("admin_print_scripts-$page", array($this, 'dequeue_pd_scripts'));
252
+
253
  //Make a placeholder for our screen options (hacky)
254
  add_meta_box("ws-ame-screen-options", "You should never see this", array(&$this, 'noop'), $page);
255
  }
821
  if ( !empty($topmenu['separator']) && !$first_nonseparator_found ) continue;
822
 
823
  $first_nonseparator_found = true;
824
+
825
+ //Menus that have both a custom icon URL and a "menu-icon-*" class will get two overlapping icons.
826
+ //Fix this by automatically removing the class. The user can set a custom class attr. to override.
827
+ if (
828
+ ameMenuItem::is_default($topmenu, 'css_class')
829
+ && !ameMenuItem::is_default($topmenu, 'icon_url')
830
+ && !in_array($topmenu['icon_url'], array('', 'none', 'div')) //Skip "no custom icon" icons.
831
+ ) {
832
+ $new_classes = preg_replace('@\bmenu-icon-[^\s]+\b@', '', $topmenu['defaults']['css_class']);
833
+ if ( $new_classes !== $topmenu['defaults']['css_class'] ) {
834
+ $topmenu['css_class'] = $new_classes;
835
+ }
836
+ }
837
 
838
  //Apply defaults & filters
839
  $topmenu = $this->apply_defaults($topmenu);
840
  $topmenu = $this->apply_menu_filters($topmenu, 'menu');
841
+
842
  //Skip hidden entries
843
  if (!empty($topmenu['hidden'])) continue;
844
 
1199
  echo implode("\n", $pageSelector);
1200
  ?>
1201
 
1202
+ <!-- Menu icon selector widget -->
1203
+ <div id="ws_icon_selector" style="display: none;">
1204
+ <?php
1205
+ //Let the user select a custom icon via the media uploader.
1206
+ //We only support the new WP 3.5+ media API. Hence the function_exists() check.
1207
+ if ( function_exists('wp_enqueue_media') ):
1208
+ ?>
1209
+ <input type="button" class="button"
1210
+ id="ws_choose_icon_from_media"
1211
+ title="Upload an image or choose one from your media library"
1212
+ value="Choose Icon">
1213
+ <div class="clear"></div>
1214
+ <?php
1215
+ endif;
1216
+ ?>
1217
+
1218
+ <?php
1219
+ $defaultWpIcons = array(
1220
+ 'generic', 'dashboard', 'post', 'media', 'links', 'page', 'comments',
1221
+ 'appearance', 'plugins', 'users', 'tools', 'settings', 'site',
1222
+ );
1223
+ foreach($defaultWpIcons as $icon) {
1224
+ printf(
1225
+ '<div class="ws_icon_option" title="%1$s" data-icon-class="menu-icon-%2$s">
1226
+ <div class="ws_icon_image icon16 icon-%2$s"><br></div>
1227
+ </div>',
1228
+ esc_attr(ucwords($icon)),
1229
+ $icon
1230
+ );
1231
+ }
1232
+
1233
+ $defaultIconImages = array(
1234
+ 'images/generic.png',
1235
+ );
1236
+ foreach($defaultIconImages as $icon) {
1237
+ printf(
1238
+ '<div class="ws_icon_option" data-icon-url="%1$s">
1239
+ <img src="%1$s">
1240
+ </div>',
1241
+ esc_attr($icon)
1242
+ );
1243
+ }
1244
+ ?>
1245
+ <div class="ws_icon_option ws_custom_image_icon" title="Custom image" style="display: none;">
1246
+ <img src="<?php echo esc_attr(admin_url('images/loading.gif')); ?>" alt="Custom image">
1247
+ </div>
1248
+ <div class="clear"></div>
1249
+ </div>
1250
+
1251
  <span id="ws-ame-screen-meta-contents" style="display:none;">
1252
  <label for="ws-hide-advanced-settings">
1253
  <input type="checkbox" id="ws-hide-advanced-settings"<?php
includes/menu-item.php ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * This class contains a number of static methods for working with individual menu items.
5
+ *
6
+ * Note: This class is not fully self-contained. Some of the methods will query global state.
7
+ * This is necessary because the interpretation of certain menu fields depends on things like
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
+ *
14
+ * @param array $item An menu item.
15
+ * @param int $position The position (index) of the the menu item.
16
+ * @param string $parent The slug of the parent menu that owns this item. Blank for top level menus.
17
+ * @return array
18
+ */
19
+ public static function fromWpItem($item, $position = 0, $parent = '') {
20
+ static $separator_count = 0;
21
+ $item = array(
22
+ 'menu_title' => $item[0],
23
+ 'access_level' => $item[1], //= required capability
24
+ 'file' => $item[2],
25
+ 'page_title' => (isset($item[3]) ? $item[3] : ''),
26
+ 'css_class' => (isset($item[4]) ? $item[4] : 'menu-top'),
27
+ 'hookname' => (isset($item[5]) ? $item[5] : ''), //Used as the ID attr. of the generated HTML tag.
28
+ 'icon_url' => (isset($item[6]) ? $item[6] : 'images/generic.png'),
29
+ 'position' => $position,
30
+ 'parent' => $parent,
31
+ );
32
+
33
+ if ( is_numeric($item['access_level']) ) {
34
+ $dummyUser = new WP_User;
35
+ $item['access_level'] = $dummyUser->translate_level_to_cap($item['access_level']);
36
+ }
37
+
38
+ if ( empty($parent) ) {
39
+ $item['separator'] = empty($item['file']) || empty($item['menu_title']) || (strpos($item['css_class'], 'wp-menu-separator') !== false);
40
+ //WP 3.0 in multisite mode has two separators with the same filename. Fix by reindexing separators.
41
+ if ( $item['separator'] ) {
42
+ $item['file'] = 'separator_' . ($separator_count++);
43
+ }
44
+ } else {
45
+ //Submenus can't contain separators.
46
+ $item['separator'] = false;
47
+ }
48
+
49
+ //Flag plugin pages
50
+ $item['is_plugin_page'] = (get_plugin_page_hook($item['file'], $parent) != null);
51
+
52
+ if ( !$item['separator'] ) {
53
+ $item['url'] = self::generate_url($item['file'], $parent);
54
+ }
55
+
56
+ $item['template_id'] = self::template_id($item, $parent);
57
+
58
+ return array_merge(self::basic_defaults(), $item);
59
+ }
60
+
61
+ public static function basic_defaults() {
62
+ static $basic_defaults = null;
63
+ if ( $basic_defaults !== null ) {
64
+ return $basic_defaults;
65
+ }
66
+
67
+ $basic_defaults = array(
68
+ //Fields that apply to all menu items.
69
+ 'page_title' => '',
70
+ 'menu_title' => '',
71
+ 'access_level' => 'read',
72
+ 'extra_capability' => '',
73
+ 'file' => '',
74
+ 'position' => 0,
75
+ 'parent' => '',
76
+
77
+ //Fields that apply only to top level menus.
78
+ 'css_class' => 'menu-top',
79
+ 'hookname' => '',
80
+ 'icon_url' => 'images/generic.png',
81
+ 'separator' => false,
82
+
83
+ //Internal fields that may not map directly to WP menu structures.
84
+ 'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
85
+ 'template_id' => '', //The default menu item that this item is based on.
86
+ 'is_plugin_page' => false,
87
+ 'custom' => false,
88
+ 'url' => '',
89
+ );
90
+
91
+ return $basic_defaults;
92
+ }
93
+
94
+ public static function blank_menu() {
95
+ static $blank_menu = null;
96
+ if ( $blank_menu !== null ) {
97
+ return $blank_menu;
98
+ }
99
+
100
+ //Template for a basic menu item.
101
+ $blank_menu = array_fill_keys(array_keys(self::basic_defaults()), null);
102
+ $blank_menu = array_merge($blank_menu, array(
103
+ 'items' => array(), //List of sub-menu items.
104
+ 'grant_access' => array(), //Per-role and per-user access. Supersedes role_access.
105
+ 'role_access' => array(), //Per-role access settings.
106
+
107
+ 'custom' => false, //True if item is made-from-scratch and has no template.
108
+ 'missing' => false, //True if our template is no longer present in the default admin menu. Note: Stored values will be ignored. Set upon merging.
109
+ 'unused' => false, //True if this item was generated from an unused default menu. Note: Stored values will be ignored. Set upon merging.
110
+ 'hidden' => false, //Hide/show the item. Hiding is purely cosmetic, the item remains accessible.
111
+
112
+ 'defaults' => self::basic_defaults(),
113
+ ));
114
+ return $blank_menu;
115
+ }
116
+
117
+ public static function custom_item_defaults() {
118
+ return array(
119
+ 'menu_title' => 'Custom Menu',
120
+ 'access_level' => 'read',
121
+ 'page_title' => '',
122
+ 'css_class' => 'menu-top',
123
+ 'hookname' => '',
124
+ 'icon_url' => 'images/generic.png',
125
+ 'open_in' => 'same_window',
126
+ 'is_plugin_page' => false,
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Get the value of a menu/submenu field.
132
+ * Will return the corresponding value from the 'defaults' entry of $item if the
133
+ * specified field is not set in the item itself.
134
+ *
135
+ * @param array $item
136
+ * @param string $field_name
137
+ * @param mixed $default Returned if the requested field is not set and is not listed in $item['defaults']. Defaults to null.
138
+ * @return mixed Field value.
139
+ */
140
+ public static function get($item, $field_name, $default = null){
141
+ if ( isset($item[$field_name]) ){
142
+ return $item[$field_name];
143
+ } else {
144
+ if ( isset($item['defaults'], $item['defaults'][$field_name]) ){
145
+ return $item['defaults'][$field_name];
146
+ } else {
147
+ return $default;
148
+ }
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Generate or retrieve an ID that semi-uniquely identifies the template
154
+ * of the given menu item.
155
+ *
156
+ * Note that custom items (i.e. those that do not point to any of the default
157
+ * admin menu pages) have no template IDs.
158
+ *
159
+ * The ID is generated from the item's and its parent's file attributes.
160
+ * Since WordPress technically allows two copies of the same menu to exist
161
+ * in the same sub-menu, this combination is not necessarily unique.
162
+ *
163
+ * @param array|string $item The menu item in question.
164
+ * @param string $parent_file The parent menu. If omitted, $item['defaults']['parent'] will be used.
165
+ * @return string Template ID, or an empty string if this is a custom item.
166
+ */
167
+ public static function template_id($item, $parent_file = ''){
168
+ if (is_string($item)) {
169
+ return $parent_file . '>' . $item;
170
+ }
171
+
172
+ if ( self::get($item, 'custom') ) {
173
+ return '';
174
+ }
175
+
176
+ //Maybe it already has an ID?
177
+ $template_id = self::get($item, 'template_id');
178
+ if ( !empty($template_id) ) {
179
+ return $template_id;
180
+ }
181
+
182
+ if ( isset($item['defaults']['file']) ) {
183
+ $item_file = $item['defaults']['file'];
184
+ } else {
185
+ $item_file = self::get($item, 'file');
186
+ }
187
+
188
+ if ( empty($parent_file) ) {
189
+ if ( isset($item['defaults']['parent']) ) {
190
+ $parent_file = $item['defaults']['parent'];
191
+ } else {
192
+ $parent_file = self::get($item, 'parent');
193
+ }
194
+ }
195
+
196
+ return $parent_file . '>' . $item_file;
197
+ }
198
+
199
+ /**
200
+ * Set all undefined menu fields to the default value.
201
+ *
202
+ * @param array $item Menu item in the plugin's internal form
203
+ * @return array
204
+ */
205
+ public static function apply_defaults($item){
206
+ foreach($item as $key => $value){
207
+ //Is the field set?
208
+ if ($value === null){
209
+ //Use default, if available
210
+ if (isset($item['defaults'], $item['defaults'][$key])){
211
+ $item[$key] = $item['defaults'][$key];
212
+ }
213
+ }
214
+ }
215
+ return $item;
216
+ }
217
+
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).
228
+ * @param string $item_type 'menu' or 'submenu'
229
+ * @param mixed $extra Optional extra data to pass to hooks.
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
+ /**
242
+ * Recursively normalize a menu item and all of its sub-items.
243
+ *
244
+ * This will also ensure that the item has all the required fields.
245
+ *
246
+ * @static
247
+ * @param array $item
248
+ * @return array
249
+ */
250
+ public static function normalize($item) {
251
+ if ( isset($item['defaults']) ) {
252
+ $item['defaults'] = array_merge(self::basic_defaults(), $item['defaults']);
253
+ }
254
+ $item = array_merge(self::blank_menu(), $item);
255
+
256
+ $item['unused'] = false;
257
+ $item['missing'] = false;
258
+ $item['template_id'] = self::template_id($item);
259
+
260
+ //Items pointing to a default page can't have a custom file/URL.
261
+ if ( ($item['template_id'] !== '') && ($item['file'] !== null) ) {
262
+ if ( $item['file'] == $item['defaults']['file'] ) {
263
+ //Identical to default, so just set it to use that.
264
+ $item['file'] = null;
265
+ } else {
266
+ //Different file = convert to a custom item. Need to call fix_defaults()
267
+ //to fix other fields that are currently set to defaults custom items don't have.
268
+ $item['template_id'] = '';
269
+ }
270
+ }
271
+
272
+ $item['custom'] = $item['custom'] || ($item['template_id'] == '');
273
+ $item = self::fix_defaults($item);
274
+
275
+ //Older versions would allow the user to set the required capability directly.
276
+ //This was incorrect since for default menu items the default cap was *always*
277
+ //applied anyway, and the new cap was applied on top of that. We make that explicit
278
+ //by storing the custom cap in a separate field - extra_capability - and keeping
279
+ //access_level (required cap) at the default value.
280
+ if ( isset($item['defaults']) && $item['access_level'] !== null ) {
281
+ if ( empty($item['extra_capability']) ) {
282
+ $item['extra_capability'] = $item['access_level'];
283
+ }
284
+ $item['access_level'] = null;
285
+ }
286
+
287
+ //Convert per-role access settings to the more general grant_access format.
288
+ if ( isset($item['role_access']) ) {
289
+ foreach($item['role_access'] as $role_id => $has_access) {
290
+ $item['grant_access']['role:' . $role_id] = $has_access;
291
+ }
292
+ $item['role_access'] = array();
293
+ }
294
+
295
+ if ( isset($item['items']) ) {
296
+ foreach($item['items'] as $index => $sub_item) {
297
+ $item['items'][$index] = self::normalize($sub_item);
298
+ }
299
+ }
300
+
301
+ return $item;
302
+ }
303
+
304
+ /**
305
+ * Fix obsolete default values on custom items.
306
+ *
307
+ * In older versions of the plugin, each custom item had its own set of defaults.
308
+ * It was also possible to create a pseudo-custom item from a default item by
309
+ * freely overwriting its fields with custom values.
310
+ *
311
+ * The current version uses the same defaults for all custom items. To avoid data
312
+ * loss, we'll check for any mismatches and make such defaults explicit.
313
+ *
314
+ * @static
315
+ * @param array $item
316
+ * @return array
317
+ */
318
+ private static function fix_defaults($item) {
319
+ if ( $item['custom'] && isset($item['defaults']) ) {
320
+ $new_defaults = self::custom_item_defaults();
321
+ foreach($item as $field => $value) {
322
+ $is_mismatch = is_null($value)
323
+ && array_key_exists($field, $item['defaults'])
324
+ && (
325
+ !array_key_exists($field, $new_defaults) //No default.
326
+ || ($item['defaults'][$field] != $new_defaults[$field]) //Different default.
327
+ );
328
+
329
+ if ( $is_mismatch ) {
330
+ $item[$field] = $item['defaults'][$field];
331
+ }
332
+ }
333
+ $item['defaults'] = $new_defaults;
334
+ }
335
+ return $item;
336
+ }
337
+
338
+ /**
339
+ * Custom comparison function that compares menu items based on their position in the menu.
340
+ *
341
+ * @param array $a
342
+ * @param array $b
343
+ * @return int
344
+ */
345
+ public static function compare_position($a, $b){
346
+ return self::get($a, 'position', 0) - self::get($b, 'position', 0);
347
+ }
348
+
349
+ /**
350
+ * Generate a URL for a menu item.
351
+ *
352
+ * @param string $item_slug
353
+ * @param string $parent_slug
354
+ * @return string An URL relative to the /wp-admin/ directory.
355
+ */
356
+ public static function generate_url($item_slug, $parent_slug = '') {
357
+ $menu_url = is_array($item_slug) ? self::get($item_slug, 'file') : $item_slug;
358
+ $parent_url = !empty($parent_slug) ? $parent_slug : 'admin.php';
359
+
360
+ if ( strpos($menu_url, '://') !== false ) {
361
+ return $menu_url;
362
+ }
363
+
364
+ if ( self::is_hook_or_plugin_page($menu_url, $parent_url) ) {
365
+ $base_file = self::is_hook_or_plugin_page($parent_url) ? 'admin.php' : $parent_url;
366
+ $url = add_query_arg(array('page' => $menu_url), $base_file);
367
+ } else {
368
+ $url = $menu_url;
369
+ }
370
+ return $url;
371
+ }
372
+
373
+ private static function is_hook_or_plugin_page($page_url, $parent_page_url = '') {
374
+ if ( empty($parent_page_url) ) {
375
+ $parent_page_url = 'admin.php';
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
+ /**
387
+ * Check if a field is currently set to its default value.
388
+ *
389
+ * @param array $item
390
+ * @param string $field_name
391
+ * @return bool
392
+ */
393
+ public static function is_default($item, $field_name) {
394
+ if ( isset($item[$field_name]) ){
395
+ return false;
396
+ } else {
397
+ return isset($item['defaults'], $item['defaults'][$field_name]);
398
+ }
399
+ }
400
+
401
+ public static function remove_query_from($url) {
402
+ $pos = strpos($url, '?');
403
+ if ( $pos !== false ) {
404
+ return substr($url, 0, $pos);
405
+ }
406
+ return $url;
407
+ }
408
+ }
js/menu-editor.js CHANGED
@@ -272,20 +272,20 @@ var knownMenuFields = {
272
  defaultValue: '',
273
  visible: true
274
  },
275
- 'hookname' : {
276
- caption: 'Hook name',
277
  standardCaption : true,
278
  advanced : true,
279
- type : 'text',
280
- defaultValue: '',
281
  visible: true
282
  },
283
- 'icon_url' : {
284
- caption: 'Icon URL',
285
  standardCaption : true,
286
  advanced : true,
287
  type : 'text',
288
- defaultValue: 'div',
289
  visible: true
290
  },
291
  'custom' : {
@@ -312,6 +312,9 @@ function buildEditboxFields(containerNode, entry){
312
  }
313
 
314
  for (var field_name in fields){
 
 
 
315
  var field = buildEditboxField(entry, field_name, fields[field_name]);
316
  if (field){
317
  if (fields[field_name].advanced){
@@ -319,6 +322,10 @@ function buildEditboxFields(containerNode, entry){
319
  } else {
320
  basicFields.append(field);
321
  }
 
 
 
 
322
  }
323
  }
324
 
@@ -344,6 +351,7 @@ function buildEditboxField(entry, field_name, field_settings){
344
 
345
  //Build a form field of the appropriate type
346
  var inputBox = null;
 
347
  switch(field_settings.type){
348
  case 'select':
349
  inputBox = $('<select class="ws_field_value">');
@@ -353,7 +361,7 @@ function buildEditboxField(entry, field_name, field_settings){
353
  .val(field_settings.options[optionTitle])
354
  .text(optionTitle);
355
  if ( field_settings.options[optionTitle] == value ){
356
- option.attr('selected', 'selected');
357
  }
358
  option.appendTo(inputBox);
359
  }
@@ -364,10 +372,15 @@ function buildEditboxField(entry, field_name, field_settings){
364
  field_settings.caption+'</label>'
365
  );
366
  break;
 
 
 
 
 
367
 
368
  case 'text':
369
  default:
370
- inputBox = $('<input type="text" class="ws_field_value">').val(value);
371
  }
372
 
373
 
@@ -411,6 +424,32 @@ function buildEditboxField(entry, field_name, field_settings){
411
  return editField;
412
  }
413
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  /*
415
  * Get the current values of all menu fields (ignores defaults).
416
  * Returns an object containing each field as a separate property.
@@ -671,6 +710,7 @@ $(document).ready(function(){
671
  /***************************************************************************
672
  Event handlers for editor widgets
673
  ***************************************************************************/
 
674
 
675
  //Highlight the clicked menu item and show it's submenu
676
  var currentVisibleSubmenu = null;
@@ -737,7 +777,7 @@ $(document).ready(function(){
737
  input.change();
738
  }
739
  }));
740
-
741
  //When a field is edited, change it's appearance if it's contents don't match the default value.
742
  function fieldValueChange(){
743
  var input = $(this);
@@ -771,19 +811,14 @@ $(document).ready(function(){
771
  if ( value == '' ){
772
  field.find('.ws_reset_button').click();
773
  }
 
 
 
 
774
  }
775
  }
776
- $('#ws_menu_editor .ws_field_value').live('click', fieldValueChange);
777
- //jQuery 1.3.x can't catch 'change' events with live(),
778
- //so we handle that by using event delegation instead.
779
- //TODO: Update for jQuery 1.4.2 in WP 3.0
780
- $('#ws_menu_editor').change(function(event){
781
- var target = $(event.target);
782
- if ( target.is('.ws_field_value') ){
783
- fieldValueChange.call(target, event);
784
- }
785
- });
786
-
787
  //Show/hide advanced fields
788
  $('#ws_menu_editor .ws_toggle_advanced_fields').live('click', function(){
789
  var self = $(this);
@@ -949,7 +984,178 @@ $(document).ready(function(){
949
  if ( !option.attr('selected') && option.attr('value')){
950
  option.attr('selected', 'selected');
951
  }
952
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
 
954
 
955
  /*************************************************************************
@@ -1220,6 +1426,10 @@ $(document).ready(function(){
1220
  //The items's editbox is always open
1221
  menu.find('.ws_edit_link').click();
1222
  });
 
 
 
 
1223
 
1224
  function compareMenus(a, b){
1225
  function jsTrim(str){
272
  defaultValue: '',
273
  visible: true
274
  },
275
+ 'icon_url' : {
276
+ caption: 'Icon URL',
277
  standardCaption : true,
278
  advanced : true,
279
+ type : 'icon_selector',
280
+ defaultValue: 'div',
281
  visible: true
282
  },
283
+ 'hookname' : {
284
+ caption: 'Hook name',
285
  standardCaption : true,
286
  advanced : true,
287
  type : 'text',
288
+ defaultValue: '',
289
  visible: true
290
  },
291
  'custom' : {
312
  }
313
 
314
  for (var field_name in fields){
315
+ if (!fields.hasOwnProperty(field_name)) {
316
+ continue;
317
+ }
318
  var field = buildEditboxField(entry, field_name, fields[field_name]);
319
  if (field){
320
  if (fields[field_name].advanced){
322
  } else {
323
  basicFields.append(field);
324
  }
325
+
326
+ if (field_name == 'icon_url') {
327
+ updateIconField(field.find('.ws_field_value'));
328
+ }
329
  }
330
  }
331
 
351
 
352
  //Build a form field of the appropriate type
353
  var inputBox = null;
354
+ var basicTextField = '<input type="text" class="ws_field_value">';
355
  switch(field_settings.type){
356
  case 'select':
357
  inputBox = $('<select class="ws_field_value">');
361
  .val(field_settings.options[optionTitle])
362
  .text(optionTitle);
363
  if ( field_settings.options[optionTitle] == value ){
364
+ option.prop('selected', 'selected');
365
  }
366
  option.appendTo(inputBox);
367
  }
372
  field_settings.caption+'</label>'
373
  );
374
  break;
375
+
376
+ case 'icon_selector':
377
+ inputBox = $(basicTextField).val(value)
378
+ .add('<button class="button ws_select_icon" title="Select icon"><div class="icon16 icon-settings"></div><img src="" style="display:none;"></button>');
379
+ break;
380
 
381
  case 'text':
382
  default:
383
+ inputBox = $(basicTextField).val(value);
384
  }
385
 
386
 
424
  return editField;
425
  }
426
 
427
+ function updateIconField(input) {
428
+ //Display the current icon in the selector.
429
+ var container = input.closest('.ws_container');
430
+ var cssClass = container.find('.ws_edit_field-css_class .ws_field_value').val();
431
+ var iconUrl = input.val();
432
+
433
+ var selectButton = input.closest('.ws_edit_field').find('.ws_select_icon');
434
+ var cssIcon = selectButton.find('.icon16');
435
+ var imageIcon = selectButton.find('img');
436
+
437
+ var matches = cssClass.match(/\bmenu-icon-([^\s]+)\b/);
438
+ //Icon URL take precedence over icon class.
439
+ if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' ) {
440
+ cssIcon.hide();
441
+ imageIcon.prop('src', iconUrl).show();
442
+ } else if ( matches ) {
443
+ imageIcon.hide();
444
+ cssIcon.removeClass().addClass('icon16 icon-' + matches[1]).show();
445
+ } else {
446
+ //This menu has no icon at all. This is actually a valid state
447
+ //and WordPress will display a menu like that correctly.
448
+ imageIcon.hide();
449
+ cssIcon.removeClass().addClass('icon16').show();
450
+ }
451
+ }
452
+
453
  /*
454
  * Get the current values of all menu fields (ignores defaults).
455
  * Returns an object containing each field as a separate property.
710
  /***************************************************************************
711
  Event handlers for editor widgets
712
  ***************************************************************************/
713
+ var menuEditorNode = $('#ws_menu_editor');
714
 
715
  //Highlight the clicked menu item and show it's submenu
716
  var currentVisibleSubmenu = null;
777
  input.change();
778
  }
779
  }));
780
+
781
  //When a field is edited, change it's appearance if it's contents don't match the default value.
782
  function fieldValueChange(){
783
  var input = $(this);
811
  if ( value == '' ){
812
  field.find('.ws_reset_button').click();
813
  }
814
+ } else if (fieldName == 'icon_url') {
815
+ updateIconField(input);
816
+ } else if (fieldName == 'css_class') {
817
+ updateIconField(input.closest('.ws_container').find('.ws_edit_field-icon_url .ws_field_value'));
818
  }
819
  }
820
+ menuEditorNode.on('click change', '.ws_field_value', fieldValueChange);
821
+
 
 
 
 
 
 
 
 
 
822
  //Show/hide advanced fields
823
  $('#ws_menu_editor .ws_toggle_advanced_fields').live('click', function(){
824
  var self = $(this);
984
  if ( !option.attr('selected') && option.attr('value')){
985
  option.attr('selected', 'selected');
986
  }
987
+ });
988
+
989
+ /*************************************************************************
990
+ Icon selector
991
+ *************************************************************************/
992
+ var iconSelector = $('#ws_icon_selector');
993
+ var currentIconButton = null; //Keep track of the last clicked icon button.
994
+
995
+ //When the user clicks one of the available icons, update the menu item.
996
+ iconSelector.on('click', '.ws_icon_option', function() {
997
+ var selectedIcon = $(this).addClass('ws_selected_icon');
998
+ iconSelector.hide();
999
+
1000
+ //Assign the selected icon to the menu.
1001
+ if (currentIconButton) {
1002
+ var container = currentIconButton.closest('.ws_container');
1003
+ var cssClassField = container.find('.ws_edit_field-css_class .ws_field_value');
1004
+ var iconUrlField = container.find('.ws_edit_field-icon_url .ws_field_value');
1005
+
1006
+ //Remove the existing icon class, if any.
1007
+ var cssClass = cssClassField.val();
1008
+ cssClass = jsTrim( cssClass.replace(/\bmenu-icon-[^\s]+\b/, '') );
1009
+
1010
+ if (selectedIcon.data('icon-class')) {
1011
+ //Add the new class.
1012
+ cssClass = selectedIcon.data('icon-class') + ' ' + cssClass;
1013
+ //Can't have both a class and an image or we'll get two overlapping icons.
1014
+ iconUrlField.val('');
1015
+ } else if (selectedIcon.data('icon-url')) {
1016
+ iconUrlField.val(selectedIcon.data('icon-url'));
1017
+ }
1018
+ cssClassField.val(cssClass).change();
1019
+ iconUrlField.change();
1020
+ }
1021
+
1022
+ currentIconButton = null;
1023
+ });
1024
+
1025
+ //Show/hide the icon selector when the user clicks the icon button.
1026
+ menuEditorNode.on('click', '.ws_select_icon', function() {
1027
+ var button = $(this);
1028
+ //Clicking the same button a second time hides the icon list.
1029
+ if ( currentIconButton && button.is(currentIconButton) ) {
1030
+ iconSelector.hide();
1031
+ //noinspection JSUnusedAssignment
1032
+ currentIconButton = null;
1033
+ return;
1034
+ }
1035
+
1036
+ currentIconButton = button;
1037
+
1038
+ var container = currentIconButton.closest('.ws_container');
1039
+ var cssClassField = container.find('.ws_edit_field-css_class .ws_field_value');
1040
+ var iconUrlField = container.find('.ws_edit_field-icon_url .ws_field_value');
1041
+ var cssClass = cssClassField.val();
1042
+ var iconUrl = iconUrlField.val();
1043
+
1044
+ var customImageOption = iconSelector.find('.ws_custom_image_icon').hide();
1045
+
1046
+ //Highlight the currently selected icon.
1047
+ iconSelector.find('.ws_selected_icon').removeClass('ws_selected_icon');
1048
+ var matches = cssClass.match(/\bmenu-icon-([^\s]+)\b/);
1049
+ if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' ) {
1050
+ var currentIcon = iconSelector.find('.ws_icon_option img[src="' + iconUrl + '"]').first().closest('.ws_icon_option');
1051
+ if ( currentIcon.length > 0 ) {
1052
+ currentIcon.addClass('ws_selected_icon').show();
1053
+ } else {
1054
+ //Display and highlight the custom image.
1055
+ customImageOption.find('img').prop('src', iconUrl);
1056
+ customImageOption.addClass('ws_selected_icon').show().data('icon-url', iconUrl);
1057
+ }
1058
+ } else if ( matches ) {
1059
+ //Highlight the icon that corresponds to the current CSS class.
1060
+ iconSelector.find('.icon-' + matches[1]).closest('.ws_icon_option').addClass('ws_selected_icon');
1061
+ }
1062
+
1063
+ iconSelector.show();
1064
+ iconSelector.position({ //Requires jQuery UI.
1065
+ my: 'right top',
1066
+ at: 'right bottom',
1067
+ of: button
1068
+ });
1069
+ });
1070
+
1071
+ //Alternatively, use the WordPress media uploader to select a custom icon.
1072
+ //This code is based on the header selection script in /wp-admin/js/custom-header.js.
1073
+ $('#ws_choose_icon_from_media').click(function(event) {
1074
+ event.preventDefault();
1075
+ var frame = null;
1076
+
1077
+ //This option is not usable on the demo site since the filesystem is usually read-only.
1078
+ if (wsEditorData.isDemoMode) {
1079
+ alert('Sorry, image upload is disabled in demo mode!');
1080
+ return;
1081
+ }
1082
+
1083
+ //If the media frame already exists, reopen it.
1084
+ if ( frame ) {
1085
+ frame.open();
1086
+ return;
1087
+ }
1088
+
1089
+ //Create a custom media frame.
1090
+ frame = wp.media.frames.customAdminMenuIcon = wp.media({
1091
+ //Set the title of the modal.
1092
+ title: 'Choose a Custom Icon (16x16)',
1093
+
1094
+ //Tell it to show only images.
1095
+ library: {
1096
+ type: 'image'
1097
+ },
1098
+
1099
+ //Customize the submit button.
1100
+ button: {
1101
+ text: 'Set as icon', //Button text.
1102
+ close: true //Clicking the button closes the frame.
1103
+ }
1104
+ });
1105
+
1106
+ //When an image is selected, set it as the menu icon.
1107
+ frame.on( 'select', function() {
1108
+ //Grab the selected attachment.
1109
+ var attachment = frame.state().get('selection').first();
1110
+ //TODO: Warn the user if the image exceeds 16x16 pixels.
1111
+
1112
+ //Set the menu icon to the attachment URL.
1113
+ if (currentIconButton) {
1114
+ var container = currentIconButton.closest('.ws_container');
1115
+ var cssClassField = container.find('.ws_edit_field-css_class .ws_field_value');
1116
+ var iconUrlField = container.find('.ws_edit_field-icon_url .ws_field_value');
1117
+ var cssClass = cssClassField.val();
1118
+ var iconUrl = iconUrlField.val();
1119
+
1120
+ //Remove the existing icon class, if any.
1121
+ cssClass = jsTrim( cssClass.replace(/\bmenu-icon-[^\s]+\b/, '') );
1122
+ cssClassField.val(cssClass);
1123
+
1124
+ //Set the new icon URL.
1125
+ iconUrlField.val(attachment.attributes.url);
1126
+
1127
+ cssClassField.change();
1128
+ iconUrlField.change();
1129
+ }
1130
+
1131
+ currentIconButton = null;
1132
+ });
1133
+
1134
+ //If the user closes the frame by via Esc or the "X" button, clear up state.
1135
+ frame.on('escape', function(){
1136
+ currentIconButton = null;
1137
+ });
1138
+
1139
+ frame.open();
1140
+ iconSelector.hide();
1141
+ });
1142
+
1143
+ //Hide the icon selector if the user clicks outside of it.
1144
+ //Exception: Clicks on "Select icon" buttons are handled above.
1145
+ $(document).on('mouseup', function(event) {
1146
+ if ( !iconSelector.is(':visible') ) {
1147
+ return;
1148
+ }
1149
+
1150
+ if (
1151
+ !iconSelector.is(event.target)
1152
+ && iconSelector.has(event.target).length === 0
1153
+ && $(event.target).closest('.ws_select_icon').length == 0
1154
+ ) {
1155
+ iconSelector.hide();
1156
+ currentIconButton = null;
1157
+ }
1158
+ });
1159
 
1160
 
1161
  /*************************************************************************
1426
  //The items's editbox is always open
1427
  menu.find('.ws_edit_link').click();
1428
  });
1429
+
1430
+ function jsTrim(str){
1431
+ return str.replace(/^\s+|\s+$/g, "");
1432
+ }
1433
 
1434
  function compareMenus(a, b){
1435
  function jsTrim(str){
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.1.13
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/
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.2
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/
9
  */
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: 3.2
6
- Tested up to: 3.5
7
- Stable tag: 1.1.13
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,13 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
 
66
  = 1.1.13 =
67
  * Fixed a layout glitch that would cause the editor sidebar to display incorrectly in WP 3.5.
68
  * When trying to determine the current menu, the plugin will now ignore all links that contain nothing but an "#anchor". Various plugins use such links as separators and it wouldn't make sense to highlight them.
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.5.1
7
+ Stable tag: 1.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.2 =
67
+ * Added an icon drop-down that lets you pick one of the default WordPress menu icons or upload your own through the media library (only in WP 3.5+).
68
+ * Fixed misaligned button text in IE/Firefox.
69
+ * Fixed menus that have both a custom icon URL and a "menu-icon-*" class displaying two overlapping icons. You can still get this effect if you set the class and URL manually.
70
+ * Fixed a compatibility problem with Participants Database 1.4.5.2.
71
+ * Tested on WP 3.5.1 and WP 3.6-alpha.
72
+
73
  = 1.1.13 =
74
  * Fixed a layout glitch that would cause the editor sidebar to display incorrectly in WP 3.5.
75
  * When trying to determine the current menu, the plugin will now ignore all links that contain nothing but an "#anchor". Various plugins use such links as separators and it wouldn't make sense to highlight them.