Version Description
- Added a new settings page that lets you choose whether admin menu settings are per-site or network-wide, as well as specify who can access the plugin. To access this page, go to "Settings -> Menu Editor Pro" and click the small "Settings" link next to the page title.
- Added a way to show/hide advanced menu options through the settings page in addition to the "Screen Options" panel.
- Added a "Show menu access checks" option to make debugging menu permissions easier.
- Added partial WPML support. Now you can translate custom menu titles with WPML.
- The plugin will now display an error if you try to activate it when another version of it is already active.
- Added a "Target page" dropdown as an alternative to the "URL" field. To enter a custom URL, choose "Custom" from the dropdown.
- Fixed the "window title" setting only working for some menu items and not others.
- Fixed a number of bugs related to moving plugin menus around.
- Changed how the plugin stores menu settings. Note: The new format is not backwards-compatible with version 1.2.2.
Download this release
Release Info
Developer | whiteshadow |
Plugin | Admin Menu Editor |
Version | 1.3 |
Comparing to | |
See all releases |
Code changes from version 1.2.2 to 1.3
- .htaccess +0 -4
- css/admin.css +52 -0
- css/jquery.qtip.css +573 -0
- css/jquery.qtip.min.css +1 -0
- css/menu-editor.css +290 -128
- css/style-classic.css +117 -0
- css/style-wp-gray.css +211 -0
- images/arrows-dark.png +0 -0
- images/arrows.png +0 -0
- images/check-all.png +0 -0
- images/external.png +0 -0
- images/logo-medium.png +0 -0
- images/plugin_disabled.png +0 -0
- includes/access-editor-dialog.php +85 -0
- includes/admin-menu-editor-mu.php +8 -3
- includes/auto-versioning.php +136 -0
- includes/consistency-check.php +160 -0
- includes/editor-page.php +327 -0
- includes/menu-editor-core.php +1537 -1111
- includes/menu-item.php +407 -407
- includes/menu.php +155 -0
- includes/role-utils.php +63 -0
- includes/settings-page.php +163 -0
- includes/shadow_plugin_framework.php +11 -8
- includes/version-conflict-check.php +25 -0
- js/admin-helpers.js +9 -0
- js/jquery.form.js +992 -535
- js/{jquery.json-1.3.js → jquery.json.js} +0 -0
- js/jquery.qtip.js +3365 -0
- js/jquery.qtip.min.js +2 -0
- js/menu-editor.js +1685 -866
- menu-editor.php +8 -4
- readme.txt +13 -2
- screenshot-1.png +0 -0
- screenshot-3.png +0 -0
- uninstall.php +2 -2
.htaccess
DELETED
@@ -1,4 +0,0 @@
|
|
1 |
-
<IfModule mod_rewrite.c>
|
2 |
-
RewriteEngine on
|
3 |
-
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]
|
4 |
-
</IfModule>
|
|
|
|
|
|
|
|
css/admin.css
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Miscellaneous menu styles that can be used on all admin pages.
|
3 |
+
*/
|
4 |
+
|
5 |
+
/*
|
6 |
+
* Submenu separators.
|
7 |
+
*/
|
8 |
+
hr.ws-submenu-separator {
|
9 |
+
display: block;
|
10 |
+
margin: 2px 0;
|
11 |
+
padding: 0;
|
12 |
+
height: 0;
|
13 |
+
|
14 |
+
border-width: 1px 0 0 0;
|
15 |
+
border-style: solid;
|
16 |
+
border-color: #ccc;
|
17 |
+
}
|
18 |
+
|
19 |
+
/* Custom separator style suggested by a customer (Slavo) */
|
20 |
+
/*
|
21 |
+
#adminmenu .ws-submenu-separator {
|
22 |
+
border-bottom: none;
|
23 |
+
border-top: 1px dotted rgba(0,0,0,.3);
|
24 |
+
width: 90%;
|
25 |
+
}
|
26 |
+
*/
|
27 |
+
|
28 |
+
/* S2Member separator style */
|
29 |
+
/*
|
30 |
+
#adminmenu .ws-submenu-separator {
|
31 |
+
display: block;
|
32 |
+
border: 0;
|
33 |
+
margin: 1px 0 1px -5px;
|
34 |
+
padding: 0;
|
35 |
+
height: 1px;
|
36 |
+
line-height: 1px;
|
37 |
+
background: #CCCCCC;
|
38 |
+
}
|
39 |
+
*/
|
40 |
+
|
41 |
+
/* No pointer/hand on separators. */
|
42 |
+
#adminmenu li.ws-submenu-separator-wrap a {
|
43 |
+
cursor: default;
|
44 |
+
}
|
45 |
+
|
46 |
+
/* Override the bluish highlight used by WP */
|
47 |
+
#adminmenu li.ws-submenu-separator-wrap a:hover,
|
48 |
+
#adminmenu li.ws-submenu-separator-wrap a:focus
|
49 |
+
{
|
50 |
+
background-color: white;
|
51 |
+
}
|
52 |
+
|
css/jquery.qtip.css
ADDED
@@ -0,0 +1,573 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*! qTip2 - Pretty powerful tooltips - v2.0.0 - 2012-09-10
|
2 |
+
* http://craigsworks.com/projects/qtip2/
|
3 |
+
* Copyright (c) 2012 Craig Michael Thompson; Licensed MIT, GPL */
|
4 |
+
|
5 |
+
/* Fluid class for determining actual width in IE */
|
6 |
+
#qtip-rcontainer{
|
7 |
+
position: absolute;
|
8 |
+
left: -28000px;
|
9 |
+
top: -28000px;
|
10 |
+
display: block;
|
11 |
+
visibility: hidden;
|
12 |
+
}
|
13 |
+
|
14 |
+
/* Fluid class for determining actual width in IE */
|
15 |
+
#qtip-rcontainer .ui-tooltip{
|
16 |
+
display: block !important;
|
17 |
+
visibility: hidden !important;
|
18 |
+
position: static !important;
|
19 |
+
float: left !important;
|
20 |
+
}
|
21 |
+
|
22 |
+
/* Core qTip styles */
|
23 |
+
.ui-tooltip, .qtip{
|
24 |
+
position: absolute;
|
25 |
+
left: -28000px;
|
26 |
+
top: -28000px;
|
27 |
+
display: none;
|
28 |
+
|
29 |
+
max-width: 280px;
|
30 |
+
min-width: 50px;
|
31 |
+
|
32 |
+
font-size: 10.5px;
|
33 |
+
line-height: 12px;
|
34 |
+
}
|
35 |
+
|
36 |
+
.ui-tooltip-content{
|
37 |
+
position: relative;
|
38 |
+
padding: 5px 9px;
|
39 |
+
overflow: hidden;
|
40 |
+
|
41 |
+
text-align: left;
|
42 |
+
word-wrap: break-word;
|
43 |
+
}
|
44 |
+
|
45 |
+
.ui-tooltip-titlebar{
|
46 |
+
position: relative;
|
47 |
+
min-height: 14px;
|
48 |
+
padding: 5px 35px 5px 10px;
|
49 |
+
overflow: hidden;
|
50 |
+
|
51 |
+
border-width: 0 0 1px;
|
52 |
+
font-weight: bold;
|
53 |
+
}
|
54 |
+
|
55 |
+
.ui-tooltip-titlebar + .ui-tooltip-content{ border-top-width: 0 !important; }
|
56 |
+
|
57 |
+
/* Default close button class */
|
58 |
+
.ui-tooltip-titlebar .ui-state-default{
|
59 |
+
position: absolute;
|
60 |
+
right: 4px;
|
61 |
+
top: 50%;
|
62 |
+
margin-top: -9px;
|
63 |
+
|
64 |
+
cursor: pointer;
|
65 |
+
outline: medium none;
|
66 |
+
|
67 |
+
border-width: 1px;
|
68 |
+
border-style: solid;
|
69 |
+
}
|
70 |
+
|
71 |
+
* html .ui-tooltip-titlebar .ui-state-default{ top: 16px; } /* IE fix */
|
72 |
+
|
73 |
+
.ui-tooltip-titlebar .ui-icon,
|
74 |
+
.ui-tooltip-icon .ui-icon{
|
75 |
+
display: block;
|
76 |
+
text-indent: -1000em;
|
77 |
+
direction: ltr;
|
78 |
+
}
|
79 |
+
|
80 |
+
.ui-tooltip-icon, .ui-tooltip-icon .ui-icon{
|
81 |
+
-moz-border-radius: 3px;
|
82 |
+
-webkit-border-radius: 3px;
|
83 |
+
border-radius: 3px;
|
84 |
+
text-decoration: none;
|
85 |
+
}
|
86 |
+
|
87 |
+
.ui-tooltip-icon .ui-icon{
|
88 |
+
width: 18px;
|
89 |
+
height: 14px;
|
90 |
+
|
91 |
+
text-align: center;
|
92 |
+
text-indent: 0;
|
93 |
+
font: normal bold 10px/13px Tahoma,sans-serif;
|
94 |
+
|
95 |
+
color: inherit;
|
96 |
+
background: transparent none no-repeat -100em -100em;
|
97 |
+
}
|
98 |
+
|
99 |
+
|
100 |
+
/* Applied to 'focused' tooltips e.g. most recently displayed/interacted with */
|
101 |
+
.ui-tooltip-focus{}
|
102 |
+
|
103 |
+
/* Applied on hover of tooltips i.e. added/removed on mouseenter/mouseleave respectively */
|
104 |
+
.ui-tooltip-hover{}
|
105 |
+
|
106 |
+
/* Default tooltip style */
|
107 |
+
.ui-tooltip-default{
|
108 |
+
border-width: 1px;
|
109 |
+
border-style: solid;
|
110 |
+
border-color: #F1D031;
|
111 |
+
|
112 |
+
background-color: #FFFFA3;
|
113 |
+
color: #555;
|
114 |
+
}
|
115 |
+
|
116 |
+
.ui-tooltip-default .ui-tooltip-titlebar{
|
117 |
+
background-color: #FFEF93;
|
118 |
+
}
|
119 |
+
|
120 |
+
.ui-tooltip-default .ui-tooltip-icon{
|
121 |
+
border-color: #CCC;
|
122 |
+
background: #F1F1F1;
|
123 |
+
color: #777;
|
124 |
+
}
|
125 |
+
|
126 |
+
.ui-tooltip-default .ui-tooltip-titlebar .ui-state-hover{
|
127 |
+
border-color: #AAA;
|
128 |
+
color: #111;
|
129 |
+
}
|
130 |
+
|
131 |
+
|
132 |
+
/*! Light tooltip style */
|
133 |
+
.ui-tooltip-light{
|
134 |
+
background-color: white;
|
135 |
+
border-color: #E2E2E2;
|
136 |
+
color: #454545;
|
137 |
+
}
|
138 |
+
|
139 |
+
.ui-tooltip-light .ui-tooltip-titlebar{
|
140 |
+
background-color: #f1f1f1;
|
141 |
+
}
|
142 |
+
|
143 |
+
|
144 |
+
/*! Dark tooltip style */
|
145 |
+
.ui-tooltip-dark{
|
146 |
+
background-color: #505050;
|
147 |
+
border-color: #303030;
|
148 |
+
color: #f3f3f3;
|
149 |
+
}
|
150 |
+
|
151 |
+
.ui-tooltip-dark .ui-tooltip-titlebar{
|
152 |
+
background-color: #404040;
|
153 |
+
}
|
154 |
+
|
155 |
+
.ui-tooltip-dark .ui-tooltip-icon{
|
156 |
+
border-color: #444;
|
157 |
+
}
|
158 |
+
|
159 |
+
.ui-tooltip-dark .ui-tooltip-titlebar .ui-state-hover{
|
160 |
+
border-color: #303030;
|
161 |
+
}
|
162 |
+
|
163 |
+
|
164 |
+
/*! Cream tooltip style */
|
165 |
+
.ui-tooltip-cream{
|
166 |
+
background-color: #FBF7AA;
|
167 |
+
border-color: #F9E98E;
|
168 |
+
color: #A27D35;
|
169 |
+
}
|
170 |
+
|
171 |
+
.ui-tooltip-cream .ui-tooltip-titlebar{
|
172 |
+
background-color: #F0DE7D;
|
173 |
+
}
|
174 |
+
|
175 |
+
.ui-tooltip-cream .ui-state-default .ui-tooltip-icon{
|
176 |
+
background-position: -82px 0;
|
177 |
+
}
|
178 |
+
|
179 |
+
|
180 |
+
/*! Red tooltip style */
|
181 |
+
.ui-tooltip-red{
|
182 |
+
background-color: #F78B83;
|
183 |
+
border-color: #D95252;
|
184 |
+
color: #912323;
|
185 |
+
}
|
186 |
+
|
187 |
+
.ui-tooltip-red .ui-tooltip-titlebar{
|
188 |
+
background-color: #F06D65;
|
189 |
+
}
|
190 |
+
|
191 |
+
.ui-tooltip-red .ui-state-default .ui-tooltip-icon{
|
192 |
+
background-position: -102px 0;
|
193 |
+
}
|
194 |
+
|
195 |
+
.ui-tooltip-red .ui-tooltip-icon{
|
196 |
+
border-color: #D95252;
|
197 |
+
}
|
198 |
+
|
199 |
+
.ui-tooltip-red .ui-tooltip-titlebar .ui-state-hover{
|
200 |
+
border-color: #D95252;
|
201 |
+
}
|
202 |
+
|
203 |
+
|
204 |
+
/*! Green tooltip style */
|
205 |
+
.ui-tooltip-green{
|
206 |
+
background-color: #CAED9E;
|
207 |
+
border-color: #90D93F;
|
208 |
+
color: #3F6219;
|
209 |
+
}
|
210 |
+
|
211 |
+
.ui-tooltip-green .ui-tooltip-titlebar{
|
212 |
+
background-color: #B0DE78;
|
213 |
+
}
|
214 |
+
|
215 |
+
.ui-tooltip-green .ui-state-default .ui-tooltip-icon{
|
216 |
+
background-position: -42px 0;
|
217 |
+
}
|
218 |
+
|
219 |
+
|
220 |
+
/*! Blue tooltip style */
|
221 |
+
.ui-tooltip-blue{
|
222 |
+
background-color: #E5F6FE;
|
223 |
+
border-color: #ADD9ED;
|
224 |
+
color: #5E99BD;
|
225 |
+
}
|
226 |
+
|
227 |
+
.ui-tooltip-blue .ui-tooltip-titlebar{
|
228 |
+
background-color: #D0E9F5;
|
229 |
+
}
|
230 |
+
|
231 |
+
.ui-tooltip-blue .ui-state-default .ui-tooltip-icon{
|
232 |
+
background-position: -2px 0;
|
233 |
+
}
|
234 |
+
|
235 |
+
|
236 |
+
/* Add shadows to your tooltips in: FF3+, Chrome 2+, Opera 10.6+, IE9+, Safari 2+ */
|
237 |
+
.ui-tooltip-shadow{
|
238 |
+
-webkit-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.15);
|
239 |
+
-moz-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.15);
|
240 |
+
box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.15);
|
241 |
+
}
|
242 |
+
|
243 |
+
/* Add rounded corners to your tooltips in: FF3+, Chrome 2+, Opera 10.6+, IE9+, Safari 2+ */
|
244 |
+
.ui-tooltip-rounded,
|
245 |
+
.ui-tooltip-tipsy,
|
246 |
+
.ui-tooltip-bootstrap{
|
247 |
+
-moz-border-radius: 5px;
|
248 |
+
-webkit-border-radius: 5px;
|
249 |
+
border-radius: 5px;
|
250 |
+
}
|
251 |
+
|
252 |
+
/* Youtube tooltip style */
|
253 |
+
.ui-tooltip-youtube{
|
254 |
+
-moz-border-radius: 2px;
|
255 |
+
-webkit-border-radius: 2px;
|
256 |
+
border-radius: 2px;
|
257 |
+
|
258 |
+
-webkit-box-shadow: 0 0 3px #333;
|
259 |
+
-moz-box-shadow: 0 0 3px #333;
|
260 |
+
box-shadow: 0 0 3px #333;
|
261 |
+
|
262 |
+
color: white;
|
263 |
+
border-width: 0;
|
264 |
+
|
265 |
+
background: #4A4A4A;
|
266 |
+
background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0,#4A4A4A),color-stop(100%,black));
|
267 |
+
background-image: -webkit-linear-gradient(top,#4A4A4A 0,black 100%);
|
268 |
+
background-image: -moz-linear-gradient(top,#4A4A4A 0,black 100%);
|
269 |
+
background-image: -ms-linear-gradient(top,#4A4A4A 0,black 100%);
|
270 |
+
background-image: -o-linear-gradient(top,#4A4A4A 0,black 100%);
|
271 |
+
}
|
272 |
+
|
273 |
+
.ui-tooltip-youtube .ui-tooltip-titlebar{
|
274 |
+
background-color: #4A4A4A;
|
275 |
+
background-color: rgba(0,0,0,0);
|
276 |
+
}
|
277 |
+
|
278 |
+
.ui-tooltip-youtube .ui-tooltip-content{
|
279 |
+
padding: .75em;
|
280 |
+
font: 12px arial,sans-serif;
|
281 |
+
|
282 |
+
filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#4a4a4a,EndColorStr=#000000);
|
283 |
+
-ms-filter: "progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#4a4a4a,EndColorStr=#000000);";
|
284 |
+
}
|
285 |
+
|
286 |
+
.ui-tooltip-youtube .ui-tooltip-icon{
|
287 |
+
border-color: #222;
|
288 |
+
}
|
289 |
+
|
290 |
+
.ui-tooltip-youtube .ui-tooltip-titlebar .ui-state-hover{
|
291 |
+
border-color: #303030;
|
292 |
+
}
|
293 |
+
|
294 |
+
|
295 |
+
/* jQuery TOOLS Tooltip style */
|
296 |
+
.ui-tooltip-jtools{
|
297 |
+
background: #232323;
|
298 |
+
background: rgba(0, 0, 0, 0.7);
|
299 |
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#717171), to(#232323));
|
300 |
+
background-image: -moz-linear-gradient(top, #717171, #232323);
|
301 |
+
background-image: -webkit-linear-gradient(top, #717171, #232323);
|
302 |
+
background-image: -ms-linear-gradient(top, #717171, #232323);
|
303 |
+
background-image: -o-linear-gradient(top, #717171, #232323);
|
304 |
+
|
305 |
+
border: 2px solid #ddd;
|
306 |
+
border: 2px solid rgba(241,241,241,1);
|
307 |
+
|
308 |
+
-moz-border-radius: 2px;
|
309 |
+
-webkit-border-radius: 2px;
|
310 |
+
border-radius: 2px;
|
311 |
+
|
312 |
+
-webkit-box-shadow: 0 0 12px #333;
|
313 |
+
-moz-box-shadow: 0 0 12px #333;
|
314 |
+
box-shadow: 0 0 12px #333;
|
315 |
+
}
|
316 |
+
|
317 |
+
/* IE Specific */
|
318 |
+
.ui-tooltip-jtools .ui-tooltip-titlebar{
|
319 |
+
background-color: transparent;
|
320 |
+
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171,endColorstr=#4A4A4A);
|
321 |
+
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171,endColorstr=#4A4A4A)";
|
322 |
+
}
|
323 |
+
.ui-tooltip-jtools .ui-tooltip-content{
|
324 |
+
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A,endColorstr=#232323);
|
325 |
+
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A,endColorstr=#232323)";
|
326 |
+
}
|
327 |
+
|
328 |
+
.ui-tooltip-jtools .ui-tooltip-titlebar,
|
329 |
+
.ui-tooltip-jtools .ui-tooltip-content{
|
330 |
+
background: transparent;
|
331 |
+
color: white;
|
332 |
+
border: 0 dashed transparent;
|
333 |
+
}
|
334 |
+
|
335 |
+
.ui-tooltip-jtools .ui-tooltip-icon{
|
336 |
+
border-color: #555;
|
337 |
+
}
|
338 |
+
|
339 |
+
.ui-tooltip-jtools .ui-tooltip-titlebar .ui-state-hover{
|
340 |
+
border-color: #333;
|
341 |
+
}
|
342 |
+
|
343 |
+
|
344 |
+
/* Cluetip style */
|
345 |
+
.ui-tooltip-cluetip{
|
346 |
+
-webkit-box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.4);
|
347 |
+
-moz-box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.4);
|
348 |
+
box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.4);
|
349 |
+
|
350 |
+
background-color: #D9D9C2;
|
351 |
+
color: #111;
|
352 |
+
border: 0 dashed transparent;
|
353 |
+
}
|
354 |
+
|
355 |
+
.ui-tooltip-cluetip .ui-tooltip-titlebar{
|
356 |
+
background-color: #87876A;
|
357 |
+
color: white;
|
358 |
+
border: 0 dashed transparent;
|
359 |
+
}
|
360 |
+
|
361 |
+
.ui-tooltip-cluetip .ui-tooltip-icon{
|
362 |
+
border-color: #808064;
|
363 |
+
}
|
364 |
+
|
365 |
+
.ui-tooltip-cluetip .ui-tooltip-titlebar .ui-state-hover{
|
366 |
+
border-color: #696952;
|
367 |
+
color: #696952;
|
368 |
+
}
|
369 |
+
|
370 |
+
|
371 |
+
/* Tipsy style */
|
372 |
+
.ui-tooltip-tipsy{
|
373 |
+
background: black;
|
374 |
+
background: rgba(0, 0, 0, .87);
|
375 |
+
|
376 |
+
color: white;
|
377 |
+
border: 0 solid transparent;
|
378 |
+
|
379 |
+
font-size: 11px;
|
380 |
+
font-family: 'Lucida Grande', sans-serif;
|
381 |
+
font-weight: bold;
|
382 |
+
line-height: 16px;
|
383 |
+
text-shadow: 0 1px black;
|
384 |
+
}
|
385 |
+
|
386 |
+
.ui-tooltip-tipsy .ui-tooltip-titlebar{
|
387 |
+
padding: 6px 35px 0 10;
|
388 |
+
background-color: transparent;
|
389 |
+
}
|
390 |
+
|
391 |
+
.ui-tooltip-tipsy .ui-tooltip-content{
|
392 |
+
padding: 6px 10;
|
393 |
+
}
|
394 |
+
|
395 |
+
.ui-tooltip-tipsy .ui-tooltip-icon{
|
396 |
+
border-color: #222;
|
397 |
+
text-shadow: none;
|
398 |
+
}
|
399 |
+
|
400 |
+
.ui-tooltip-tipsy .ui-tooltip-titlebar .ui-state-hover{
|
401 |
+
border-color: #303030;
|
402 |
+
}
|
403 |
+
|
404 |
+
|
405 |
+
/* Tipped style */
|
406 |
+
.ui-tooltip-tipped{
|
407 |
+
border: 3px solid #959FA9;
|
408 |
+
|
409 |
+
-moz-border-radius: 3px;
|
410 |
+
-webkit-border-radius: 3px;
|
411 |
+
border-radius: 3px;
|
412 |
+
|
413 |
+
background-color: #F9F9F9;
|
414 |
+
color: #454545;
|
415 |
+
|
416 |
+
font-weight: normal;
|
417 |
+
font-family: serif;
|
418 |
+
}
|
419 |
+
|
420 |
+
.ui-tooltip-tipped .ui-tooltip-titlebar{
|
421 |
+
border-bottom-width: 0;
|
422 |
+
|
423 |
+
color: white;
|
424 |
+
background: #3A79B8;
|
425 |
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#3A79B8), to(#2E629D));
|
426 |
+
background-image: -webkit-linear-gradient(top, #3A79B8, #2E629D);
|
427 |
+
background-image: -moz-linear-gradient(top, #3A79B8, #2E629D);
|
428 |
+
background-image: -ms-linear-gradient(top, #3A79B8, #2E629D);
|
429 |
+
background-image: -o-linear-gradient(top, #3A79B8, #2E629D);
|
430 |
+
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8,endColorstr=#2E629D);
|
431 |
+
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8,endColorstr=#2E629D)";
|
432 |
+
}
|
433 |
+
|
434 |
+
.ui-tooltip-tipped .ui-tooltip-icon{
|
435 |
+
border: 2px solid #285589;
|
436 |
+
background: #285589;
|
437 |
+
}
|
438 |
+
|
439 |
+
.ui-tooltip-tipped .ui-tooltip-icon .ui-icon{
|
440 |
+
background-color: #FBFBFB;
|
441 |
+
color: #555;
|
442 |
+
}
|
443 |
+
|
444 |
+
|
445 |
+
/**
|
446 |
+
* Twitter Bootstrap style.
|
447 |
+
*
|
448 |
+
* Tested with IE 8, IE 9, Chrome 18, Firefox 9, Opera 11.
|
449 |
+
* Does not work with IE 7.
|
450 |
+
*/
|
451 |
+
.ui-tooltip-bootstrap{
|
452 |
+
font-size: 13px;
|
453 |
+
line-height: 18px;
|
454 |
+
|
455 |
+
color: #333333;
|
456 |
+
background-color: #ffffff;
|
457 |
+
|
458 |
+
|
459 |
+
border: 1px solid #ccc;
|
460 |
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
461 |
+
|
462 |
+
*border-right-width: 2px;
|
463 |
+
*border-bottom-width: 2px;
|
464 |
+
|
465 |
+
-webkit-border-radius: 5px;
|
466 |
+
-moz-border-radius: 5px;
|
467 |
+
border-radius: 5px;
|
468 |
+
|
469 |
+
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
470 |
+
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
471 |
+
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
472 |
+
|
473 |
+
-webkit-background-clip: padding-box;
|
474 |
+
-moz-background-clip: padding;
|
475 |
+
background-clip: padding-box;
|
476 |
+
}
|
477 |
+
|
478 |
+
.ui-tooltip-bootstrap .ui-tooltip-titlebar{
|
479 |
+
font-size: 18px;
|
480 |
+
line-height: 22px;
|
481 |
+
|
482 |
+
border-bottom: 1px solid #ccc;
|
483 |
+
background-color: transparent;
|
484 |
+
}
|
485 |
+
|
486 |
+
.ui-tooltip-bootstrap .ui-tooltip-titlebar .ui-state-default{
|
487 |
+
right: 9px; top: 49%;
|
488 |
+
border-style: none;
|
489 |
+
}
|
490 |
+
|
491 |
+
.ui-tooltip-bootstrap .ui-tooltip-icon{
|
492 |
+
background: white;
|
493 |
+
}
|
494 |
+
|
495 |
+
.ui-tooltip-bootstrap .ui-tooltip-icon .ui-icon{
|
496 |
+
width: auto;
|
497 |
+
height: auto;
|
498 |
+
float: right;
|
499 |
+
font-size: 20px;
|
500 |
+
font-weight: bold;
|
501 |
+
line-height: 18px;
|
502 |
+
color: #000000;
|
503 |
+
text-shadow: 0 1px 0 #ffffff;
|
504 |
+
opacity: 0.2;
|
505 |
+
filter: alpha(opacity=20);
|
506 |
+
}
|
507 |
+
|
508 |
+
.ui-tooltip-bootstrap .ui-tooltip-icon .ui-icon:hover{
|
509 |
+
color: #000000;
|
510 |
+
text-decoration: none;
|
511 |
+
cursor: pointer;
|
512 |
+
opacity: 0.4;
|
513 |
+
filter: alpha(opacity=40);
|
514 |
+
}
|
515 |
+
|
516 |
+
|
517 |
+
/* IE9 fix - removes all filters */
|
518 |
+
.ui-tooltip:not(.ie9haxors) div.ui-tooltip-content,
|
519 |
+
.ui-tooltip:not(.ie9haxors) div.ui-tooltip-titlebar{
|
520 |
+
filter: none;
|
521 |
+
-ms-filter: none;
|
522 |
+
}
|
523 |
+
|
524 |
+
|
525 |
+
/* Tips plugin */
|
526 |
+
.ui-tooltip .ui-tooltip-tip{
|
527 |
+
margin: 0 auto;
|
528 |
+
overflow: hidden;
|
529 |
+
z-index: 10;
|
530 |
+
}
|
531 |
+
|
532 |
+
.ui-tooltip .ui-tooltip-tip,
|
533 |
+
.ui-tooltip .ui-tooltip-tip .qtip-vml{
|
534 |
+
position: absolute;
|
535 |
+
|
536 |
+
line-height: 0.1px !important;
|
537 |
+
font-size: 0.1px !important;
|
538 |
+
color: #123456;
|
539 |
+
|
540 |
+
background: transparent;
|
541 |
+
border: 0 dashed transparent;
|
542 |
+
}
|
543 |
+
|
544 |
+
.ui-tooltip .ui-tooltip-tip canvas{ top: 0; left: 0; }
|
545 |
+
|
546 |
+
.ui-tooltip .ui-tooltip-tip .qtip-vml{
|
547 |
+
behavior: url(#default#VML);
|
548 |
+
display: inline-block;
|
549 |
+
visibility: visible;
|
550 |
+
}
|
551 |
+
/* Modal plugin */
|
552 |
+
#qtip-overlay{
|
553 |
+
position: fixed;
|
554 |
+
left: -10000em;
|
555 |
+
top: -10000em;
|
556 |
+
}
|
557 |
+
|
558 |
+
/* Applied to modals with show.modal.blur set to true */
|
559 |
+
#qtip-overlay.blurs{ cursor: pointer; }
|
560 |
+
|
561 |
+
/* Change opacity of overlay here */
|
562 |
+
#qtip-overlay div{
|
563 |
+
position: absolute;
|
564 |
+
left: 0; top: 0;
|
565 |
+
width: 100%; height: 100%;
|
566 |
+
|
567 |
+
background-color: black;
|
568 |
+
|
569 |
+
opacity: 0.7;
|
570 |
+
filter:alpha(opacity=70);
|
571 |
+
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)";
|
572 |
+
}
|
573 |
+
|
css/jquery.qtip.min.css
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
/*! qTip2 v2.0.0 | http://craigsworks.com/projects/qtip2/ | Licensed MIT, GPL */#qtip-rcontainer{position:absolute;left:-28000px;top:-28000px;display:block;visibility:hidden}#qtip-rcontainer .ui-tooltip{display:block!important;visibility:hidden!important;position:static!important;float:left!important}.ui-tooltip,.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:280px;min-width:50px;font-size:10.5px;line-height:12px}.ui-tooltip-content{position:relative;padding:5px 9px;overflow:hidden;text-align:left;word-wrap:break-word}.ui-tooltip-titlebar{position:relative;min-height:14px;padding:5px 35px 5px 10px;overflow:hidden;border-width:0 0 1px;font-weight:700}.ui-tooltip-titlebar+.ui-tooltip-content{border-top-width:0!important}.ui-tooltip-titlebar .ui-state-default{position:absolute;right:4px;top:50%;margin-top:-9px;cursor:pointer;outline:medium none;border-width:1px;border-style:solid}* html .ui-tooltip-titlebar .ui-state-default{top:16px}.ui-tooltip-titlebar .ui-icon,.ui-tooltip-icon .ui-icon{display:block;text-indent:-1000em;direction:ltr}.ui-tooltip-icon,.ui-tooltip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;text-decoration:none}.ui-tooltip-icon .ui-icon{width:18px;height:14px;text-align:center;text-indent:0;font:normal bold 10px/13px Tahoma,sans-serif;color:inherit;background:transparent none no-repeat -100em -100em}.ui-tooltip-focus{}.ui-tooltip-hover{}.ui-tooltip-default{border-width:1px;border-style:solid;border-color:#F1D031;background-color:#FFFFA3;color:#555}.ui-tooltip-default .ui-tooltip-titlebar{background-color:#FFEF93}.ui-tooltip-default .ui-tooltip-icon{border-color:#CCC;background:#F1F1F1;color:#777}.ui-tooltip-default .ui-tooltip-titlebar .ui-state-hover{border-color:#AAA;color:#111}/*! Light tooltip style */.ui-tooltip-light{background-color:#fff;border-color:#E2E2E2;color:#454545}.ui-tooltip-light .ui-tooltip-titlebar{background-color:#f1f1f1}/*! Dark tooltip style */.ui-tooltip-dark{background-color:#505050;border-color:#303030;color:#f3f3f3}.ui-tooltip-dark .ui-tooltip-titlebar{background-color:#404040}.ui-tooltip-dark .ui-tooltip-icon{border-color:#444}.ui-tooltip-dark .ui-tooltip-titlebar .ui-state-hover{border-color:#303030}/*! Cream tooltip style */.ui-tooltip-cream{background-color:#FBF7AA;border-color:#F9E98E;color:#A27D35}.ui-tooltip-cream .ui-tooltip-titlebar{background-color:#F0DE7D}.ui-tooltip-cream .ui-state-default .ui-tooltip-icon{background-position:-82px 0}/*! Red tooltip style */.ui-tooltip-red{background-color:#F78B83;border-color:#D95252;color:#912323}.ui-tooltip-red .ui-tooltip-titlebar{background-color:#F06D65}.ui-tooltip-red .ui-state-default .ui-tooltip-icon{background-position:-102px 0}.ui-tooltip-red .ui-tooltip-icon{border-color:#D95252}.ui-tooltip-red .ui-tooltip-titlebar .ui-state-hover{border-color:#D95252}/*! Green tooltip style */.ui-tooltip-green{background-color:#CAED9E;border-color:#90D93F;color:#3F6219}.ui-tooltip-green .ui-tooltip-titlebar{background-color:#B0DE78}.ui-tooltip-green .ui-state-default .ui-tooltip-icon{background-position:-42px 0}/*! Blue tooltip style */.ui-tooltip-blue{background-color:#E5F6FE;border-color:#ADD9ED;color:#5E99BD}.ui-tooltip-blue .ui-tooltip-titlebar{background-color:#D0E9F5}.ui-tooltip-blue .ui-state-default .ui-tooltip-icon{background-position:-2px 0}.ui-tooltip-shadow{-webkit-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);-moz-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);box-shadow:1px 1px 3px 1px rgba(0,0,0,.15)}.ui-tooltip-rounded,.ui-tooltip-tipsy,.ui-tooltip-bootstrap{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.ui-tooltip-youtube{-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 3px #333;-moz-box-shadow:0 0 3px #333;box-shadow:0 0 3px #333;color:#fff;border-width:0;background:#4A4A4A;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0, #4A4A4A),color-stop(100%,black));background-image:-webkit-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-moz-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-ms-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-o-linear-gradient(top, #4A4A4A 0,black 100%)}.ui-tooltip-youtube .ui-tooltip-titlebar{background-color:#4A4A4A;background-color:rgba(0,0,0,0)}.ui-tooltip-youtube .ui-tooltip-content{padding:.75em;font:12px arial,sans-serif;filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);-ms-filter:"progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);"}.ui-tooltip-youtube .ui-tooltip-icon{border-color:#222}.ui-tooltip-youtube .ui-tooltip-titlebar .ui-state-hover{border-color:#303030}.ui-tooltip-jtools{background:#232323;background:rgba(0,0,0,.7);background-image:-webkit-gradient(linear,left top,left bottom,from( #717171),to( #232323));background-image:-moz-linear-gradient(top, #717171, #232323);background-image:-webkit-linear-gradient(top, #717171, #232323);background-image:-ms-linear-gradient(top, #717171, #232323);background-image:-o-linear-gradient(top, #717171, #232323);border:2px solid #ddd;border:2px solid rgba(241,241,241,1);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 12px #333;-moz-box-shadow:0 0 12px #333;box-shadow:0 0 12px #333}.ui-tooltip-jtools .ui-tooltip-titlebar{background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A)"}.ui-tooltip-jtools .ui-tooltip-content{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323)"}.ui-tooltip-jtools .ui-tooltip-titlebar,.ui-tooltip-jtools .ui-tooltip-content{background:transparent;color:#fff;border:0 dashed transparent}.ui-tooltip-jtools .ui-tooltip-icon{border-color:#555}.ui-tooltip-jtools .ui-tooltip-titlebar .ui-state-hover{border-color:#333}.ui-tooltip-cluetip{-webkit-box-shadow:4px 4px 5px rgba(0,0,0,.4);-moz-box-shadow:4px 4px 5px rgba(0,0,0,.4);box-shadow:4px 4px 5px rgba(0,0,0,.4);background-color:#D9D9C2;color:#111;border:0 dashed transparent}.ui-tooltip-cluetip .ui-tooltip-titlebar{background-color:#87876A;color:#fff;border:0 dashed transparent}.ui-tooltip-cluetip .ui-tooltip-icon{border-color:#808064}.ui-tooltip-cluetip .ui-tooltip-titlebar .ui-state-hover{border-color:#696952;color:#696952}.ui-tooltip-tipsy{background:#000;background:rgba(0,0,0,.87);color:#fff;border:0 solid transparent;font-size:11px;font-family:'Lucida Grande',sans-serif;font-weight:700;line-height:16px;text-shadow:0 1px black}.ui-tooltip-tipsy .ui-tooltip-titlebar{padding:6px 35px 0 10;background-color:transparent}.ui-tooltip-tipsy .ui-tooltip-content{padding:6px 10}.ui-tooltip-tipsy .ui-tooltip-icon{border-color:#222;text-shadow:none}.ui-tooltip-tipsy .ui-tooltip-titlebar .ui-state-hover{border-color:#303030}.ui-tooltip-tipped{border:3px solid #959FA9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-color:#F9F9F9;color:#454545;font-weight:400;font-family:serif}.ui-tooltip-tipped .ui-tooltip-titlebar{border-bottom-width:0;color:#fff;background:#3A79B8;background-image:-webkit-gradient(linear,left top,left bottom,from( #3A79B8),to( #2E629D));background-image:-webkit-linear-gradient(top, #3A79B8, #2E629D);background-image:-moz-linear-gradient(top, #3A79B8, #2E629D);background-image:-ms-linear-gradient(top, #3A79B8, #2E629D);background-image:-o-linear-gradient(top, #3A79B8, #2E629D);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D)"}.ui-tooltip-tipped .ui-tooltip-icon{border:2px solid #285589;background:#285589}.ui-tooltip-tipped .ui-tooltip-icon .ui-icon{background-color:#FBFBFB;color:#555}.ui-tooltip-bootstrap{font-size:13px;line-height:18px;color:#333;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.ui-tooltip-bootstrap .ui-tooltip-titlebar{font-size:18px;line-height:22px;border-bottom:1px solid #ccc;background-color:transparent}.ui-tooltip-bootstrap .ui-tooltip-titlebar .ui-state-default{right:9px;top:49%;border-style:none}.ui-tooltip-bootstrap .ui-tooltip-icon{background:#fff}.ui-tooltip-bootstrap .ui-tooltip-icon .ui-icon{width:auto;height:auto;float:right;font-size:20px;font-weight:700;line-height:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.ui-tooltip-bootstrap .ui-tooltip-icon .ui-icon:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}.ui-tooltip:not(.ie9haxors) div.ui-tooltip-content,.ui-tooltip:not(.ie9haxors) div.ui-tooltip-titlebar{filter:none;-ms-filter:none}.ui-tooltip .ui-tooltip-tip{margin:0 auto;overflow:hidden;z-index:10}.ui-tooltip .ui-tooltip-tip,.ui-tooltip .ui-tooltip-tip .qtip-vml{position:absolute;line-height:.1px!important;font-size:.1px!important;color:#123456;background:transparent;border:0 dashed transparent}.ui-tooltip .ui-tooltip-tip canvas{top:0;left:0}.ui-tooltip .ui-tooltip-tip .qtip-vml{behavior:url(#default#VML);display:inline-block;visibility:visible}#qtip-overlay{position:fixed;left:-10000em;top:-10000em}#qtip-overlay.blurs{cursor:pointer}#qtip-overlay div{position:absolute;left:0;top:0;width:100%;height:100%;background-color:#000;opacity:.7;filter:alpha(opacity=70);-ms-filter:"alpha(Opacity=70)"}
|
css/menu-editor.css
CHANGED
@@ -1,4 +1,8 @@
|
|
1 |
-
/* Admin Menu Editor CSS file */
|
|
|
|
|
|
|
|
|
2 |
|
3 |
.ws_main_container {
|
4 |
margin: 2px;
|
@@ -17,7 +21,12 @@
|
|
17 |
width: 100%;
|
18 |
margin: 0;
|
19 |
padding-top: 2px;
|
20 |
-
padding-bottom:
|
|
|
|
|
|
|
|
|
|
|
21 |
}
|
22 |
|
23 |
#ws_menu_box {
|
@@ -26,6 +35,60 @@
|
|
26 |
#ws_submenu_box {
|
27 |
}
|
28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
/*
|
30 |
* The sidebar
|
31 |
*/
|
@@ -35,18 +98,18 @@
|
|
35 |
padding: 2px;
|
36 |
}
|
37 |
|
38 |
-
#
|
39 |
clear: both;
|
40 |
display: block;
|
41 |
margin: 4px;
|
42 |
width: 120px;
|
43 |
}
|
44 |
|
45 |
-
#
|
46 |
margin-bottom: 20px;
|
47 |
}
|
48 |
|
49 |
-
#
|
50 |
margin-top: 12px;
|
51 |
}
|
52 |
|
@@ -59,28 +122,15 @@
|
|
59 |
width: 290px;
|
60 |
|
61 |
padding : 3px;
|
62 |
-
margin: 2px;
|
63 |
-
margin-left: auto;
|
64 |
-
margin-right: auto;
|
65 |
-
|
66 |
-
border: 1px solid #a9badb;
|
67 |
-
background-color: #bdd3ff;
|
68 |
}
|
69 |
|
70 |
-
.ws_active {
|
71 |
-
background-color : #8eb0f1 !important; /* make sure this overrides ws_menu_separator */
|
72 |
-
}
|
73 |
|
74 |
.ws_menu { }
|
75 |
.ws_item { }
|
76 |
|
77 |
-
.ws_menu_separator {
|
78 |
-
background-image: url("../images/menu-arrows.png");
|
79 |
-
background-repeat: no-repeat;
|
80 |
-
background-position : 4px 8px;
|
81 |
-
background-color: #F9F9F9;
|
82 |
-
border-color: #d9d9d9;
|
83 |
-
}
|
84 |
|
85 |
.ws_submenu {
|
86 |
min-height: 2em;
|
@@ -92,56 +142,25 @@
|
|
92 |
}
|
93 |
|
94 |
.ws_item_title {
|
95 |
-
display: block;
|
96 |
padding: 2px;
|
97 |
cursor: default;
|
98 |
}
|
99 |
|
100 |
.ws_edit_link {
|
101 |
float: right;
|
102 |
-
margin-right:
|
103 |
cursor: pointer;
|
104 |
display:block;
|
105 |
width: 40px;
|
106 |
height: 22px;
|
107 |
|
108 |
-
background-image: url('../images/bullet_arrow_down2.png');
|
109 |
-
background-repeat: no-repeat;
|
110 |
-
background-position: center;
|
111 |
-
|
112 |
border-radius: 3px;
|
113 |
-moz-border-radius: 3px;
|
114 |
-webkit-border-radius: 3px;
|
115 |
}
|
116 |
|
117 |
-
|
118 |
-
background-color: #ffffd0;
|
119 |
-
background-image: url('../images/bullet_arrow_down2.png');
|
120 |
-
}
|
121 |
-
|
122 |
-
.ws_edit_link:active {
|
123 |
-
background-repeat: no-repeat;
|
124 |
-
background-position: center;
|
125 |
-
}
|
126 |
-
|
127 |
-
.ws_edit_link_expanded {
|
128 |
-
background-color: #ffffd0;
|
129 |
-
border-bottom: none;
|
130 |
-
border-color: #ffffd0;
|
131 |
-
|
132 |
-
background-image: url('../images/bullet_arrow_down2.png');
|
133 |
-
padding-bottom: 1px;
|
134 |
-
background-position: center 3px;
|
135 |
-
|
136 |
-
border-bottom-right-radius: 0;
|
137 |
-
border-bottom-left-radius: 0;
|
138 |
-
|
139 |
-
-moz-border-radius-bottomright: 0;
|
140 |
-
-moz-border-radius-bottomleft: 0;
|
141 |
-
|
142 |
-
-webkit-border-bottom-right-radius: 0;
|
143 |
-
-webkit-border-bottom-left-radius: 0;
|
144 |
-
}
|
145 |
|
146 |
|
147 |
.ws_menu_drop_hover {
|
@@ -152,23 +171,51 @@ a.ws_edit_link:hover {
|
|
152 |
cursor: move !important;
|
153 |
}
|
154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
155 |
/****************************************
|
156 |
Per-menu settings fields & panels
|
157 |
*****************************************/
|
158 |
|
159 |
.ws_editbox {
|
160 |
display: block;
|
161 |
-
background-color: #ffffd0;
|
162 |
padding: 4px;
|
163 |
-
|
164 |
border-radius: 2px;
|
165 |
-
border-top-right-radius:
|
166 |
|
167 |
-moz-border-radius: 2px;
|
168 |
-
-moz-border-radius-topright:
|
169 |
|
170 |
-webkit-border-radius: 2px;
|
171 |
-
-webkit-border-top-right-radius:
|
172 |
}
|
173 |
|
174 |
.ws_edit_panel {
|
@@ -199,10 +246,8 @@ a.ws_edit_link:hover {
|
|
199 |
width: 16px;
|
200 |
height: 16px;
|
201 |
vertical-align: top;
|
202 |
-
|
203 |
-
background
|
204 |
-
background-repeat: no-repeat;
|
205 |
-
background-position: center;
|
206 |
}
|
207 |
|
208 |
.ws_reset_button:hover {
|
@@ -213,8 +258,9 @@ a.ws_edit_link:hover {
|
|
213 |
color: gray;
|
214 |
}
|
215 |
|
216 |
-
/* No reset button for fields set to the default value */
|
217 |
-
.ws_input_default
|
|
|
218 |
visibility: hidden;
|
219 |
}
|
220 |
|
@@ -235,16 +281,15 @@ a.ws_edit_link:hover {
|
|
235 |
}
|
236 |
|
237 |
#ws_menu_editor .ws_edit_field-custom input[type="checkbox"] {
|
238 |
-
margin-top:
|
239 |
}
|
240 |
|
241 |
/* Dropdown button for combo-box fields */
|
242 |
-
#ws_menu_editor .ws_dropdown_button
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
width: 20px;
|
247 |
-
height:
|
248 |
|
249 |
margin: 1px 1px 1px 0;
|
250 |
padding: 0;
|
@@ -257,37 +302,57 @@ a.ws_edit_link:hover {
|
|
257 |
|
258 |
border-top-right-radius: 3px;
|
259 |
border-bottom-right-radius: 3px;
|
260 |
-
border-top-left-radius:
|
261 |
-
border-bottom-left-radius:
|
262 |
|
263 |
-moz-border-radius-topright: 3px;
|
264 |
-moz-border-radius-bottomright: 3px;
|
265 |
-
-moz-border-radius-topleft:
|
266 |
-
-moz-border-radius-bottomleft:
|
267 |
|
268 |
-webkit-border-top-right-radius: 3px;
|
269 |
-webkit-border-bottom-right-radius: 3px;
|
270 |
-
-webkit-border-top-left-radius:
|
271 |
-
-webkit-border-bottom-left-radius:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
}
|
273 |
|
274 |
/*
|
275 |
-
The appearance and size of
|
276 |
-
to
|
277 |
*/
|
278 |
-
#ws_menu_editor .ws_has_dropdown input.ws_field_value
|
279 |
-
|
|
|
280 |
margin-right: 0;
|
281 |
border-right: 0;
|
282 |
|
283 |
-
border-top-right-radius:
|
284 |
-
border-bottom-right-radius:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
285 |
|
286 |
-
|
287 |
-
|
|
|
288 |
|
289 |
-
|
290 |
-
|
291 |
}
|
292 |
|
293 |
/* Unlike others, this field is just a single checkbox, so it has a smaller height */
|
@@ -338,15 +403,10 @@ to accomodate the dropdown button.
|
|
338 |
}
|
339 |
|
340 |
/* user-created items */
|
341 |
-
.
|
342 |
background-image: url('../images/page-add.png');
|
343 |
}
|
344 |
|
345 |
-
/* items not present in the default menu */
|
346 |
-
.ws_missing_flag {
|
347 |
-
background-image: url('../images/plugin_error.png');
|
348 |
-
}
|
349 |
-
|
350 |
/* unused items - those that are in the default menu but not in the custom one */
|
351 |
.ws_unused_flag {
|
352 |
background-image: url('../images/plugin_add.png');
|
@@ -358,8 +418,7 @@ to accomodate the dropdown button.
|
|
358 |
}
|
359 |
|
360 |
/* These classes could be used to apply different styles to items depending on their flags */
|
361 |
-
.
|
362 |
-
.ws_custom_item { }
|
363 |
.ws_hidden { }
|
364 |
.ws_unused { }
|
365 |
|
@@ -383,11 +442,10 @@ to accomodate the dropdown button.
|
|
383 |
display: block;
|
384 |
margin-right: 3px;
|
385 |
padding: 4px;
|
386 |
-
border: 1px solid #c0c0e0;
|
387 |
float: left;
|
388 |
|
389 |
-
|
390 |
-
|
391 |
|
392 |
border-radius: 3px;
|
393 |
-moz-border-radius: 3px;
|
@@ -408,7 +466,7 @@ a.ws_button:hover {
|
|
408 |
Capability selector
|
409 |
*************************************/
|
410 |
|
411 |
-
|
412 |
width: 252px;
|
413 |
height: 20em;
|
414 |
|
@@ -420,13 +478,13 @@ a.ws_button:hover {
|
|
420 |
font-size: 12px;
|
421 |
}
|
422 |
|
423 |
-
|
424 |
font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
|
425 |
font-size: 12px;
|
426 |
padding: 3px;
|
427 |
}
|
428 |
|
429 |
-
|
430 |
padding-left: 10px;
|
431 |
}
|
432 |
|
@@ -531,12 +589,12 @@ a.ws_button:hover {
|
|
531 |
|
532 |
/* MP6 admin style compatibility */
|
533 |
#ws_icon_selector .ws_icon_option .icon16::before {
|
534 |
-
|
535 |
-
|
536 |
}
|
537 |
.ws_select_icon .icon16::before {
|
538 |
-
|
539 |
-
|
540 |
}
|
541 |
|
542 |
#ws_choose_icon_from_media {
|
@@ -554,8 +612,8 @@ a.ws_button:hover {
|
|
554 |
.ui-widget-overlay {
|
555 |
background-color: black;
|
556 |
position: absolute;
|
557 |
-
left:
|
558 |
-
top:
|
559 |
opacity: 0.70;
|
560 |
-moz-opacity: 0.70;
|
561 |
filter: alpha(opacity=70);
|
@@ -593,11 +651,7 @@ a.ws_button:hover {
|
|
593 |
}
|
594 |
|
595 |
.ui-dialog-titlebar-close {
|
596 |
-
background
|
597 |
-
background-repeat: no-repeat;
|
598 |
-
background-position: center;
|
599 |
-
background-color: #86A7E3;
|
600 |
-
|
601 |
width: 22px;
|
602 |
height: 22px;
|
603 |
display: block;
|
@@ -623,37 +677,35 @@ a.ws_button:hover {
|
|
623 |
font-size: 1.1em;
|
624 |
}
|
625 |
|
626 |
-
.ws_dialog_panel {
|
627 |
-
height:
|
628 |
}
|
629 |
|
630 |
-
#
|
631 |
-
height:
|
632 |
}
|
633 |
|
634 |
.ws_dialog_buttons {
|
635 |
height: 23px;
|
636 |
text-align: right;
|
|
|
637 |
}
|
638 |
|
639 |
.ws_dialog_buttons .button-primary {
|
640 |
display: block;
|
641 |
float: left;
|
642 |
-
margin-top:
|
643 |
}
|
644 |
|
645 |
.ws_dialog_buttons .button {
|
646 |
-
margin-top:
|
647 |
}
|
648 |
|
649 |
#import_file_selector {
|
650 |
display: block;
|
651 |
width: 286px;
|
652 |
-
|
653 |
-
margin
|
654 |
-
margin-bottom: 12px;
|
655 |
-
margin-left: auto;
|
656 |
-
margin-right: auto;
|
657 |
}
|
658 |
|
659 |
#ws_start_import {
|
@@ -666,6 +718,117 @@ a.ws_button:hover {
|
|
666 |
padding-top: 25px;
|
667 |
}
|
668 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
669 |
/************************************
|
670 |
Tooltips and hints
|
671 |
*************************************/
|
@@ -702,8 +865,8 @@ a.ws_button:hover {
|
|
702 |
border-radius: 3px;
|
703 |
|
704 |
position: absolute;
|
705 |
-
right:
|
706 |
-
top:
|
707 |
}
|
708 |
|
709 |
.ws_hint_close:hover {
|
@@ -771,8 +934,7 @@ a.ws_button:hover {
|
|
771 |
|
772 |
/* "Upgrade to Pro" */
|
773 |
#ws-pro-version-notice {
|
774 |
-
background
|
775 |
-
background-image: none;
|
776 |
}
|
777 |
|
778 |
|
1 |
+
/* Admin Menu Editor CSS file */
|
2 |
+
|
3 |
+
#ws_menu_editor {
|
4 |
+
min-width: 780px;
|
5 |
+
}
|
6 |
|
7 |
.ws_main_container {
|
8 |
margin: 2px;
|
21 |
width: 100%;
|
22 |
margin: 0;
|
23 |
padding-top: 2px;
|
24 |
+
padding-bottom: 2px;
|
25 |
+
}
|
26 |
+
|
27 |
+
.ws_basic_container {
|
28 |
+
float: left;
|
29 |
+
display:block;
|
30 |
}
|
31 |
|
32 |
#ws_menu_box {
|
35 |
#ws_submenu_box {
|
36 |
}
|
37 |
|
38 |
+
.ws_dropzone {
|
39 |
+
margin: 0 5px 2px 5px;
|
40 |
+
border: none;
|
41 |
+
height: 25px;
|
42 |
+
}
|
43 |
+
|
44 |
+
.ws_dropzone_hover {
|
45 |
+
border: 1px dotted silver;
|
46 |
+
background: yellow;
|
47 |
+
height: 30px;
|
48 |
+
}
|
49 |
+
|
50 |
+
/*************************************************
|
51 |
+
Actor UI
|
52 |
+
*************************************************/
|
53 |
+
#ws_actor_selector li:after {
|
54 |
+
content: ' | ';
|
55 |
+
}
|
56 |
+
|
57 |
+
#ws_actor_selector li:last-child:after {
|
58 |
+
content: '';
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* The checkbox that lets the user show/hide a menu for the currently selected actor.
|
63 |
+
*/
|
64 |
+
#ws_menu_editor .ws_actor_access_checkbox,
|
65 |
+
#ws_menu_editor input[type="checkbox"].ws_actor_access_checkbox /* Ensure we override WP defaults. */
|
66 |
+
{
|
67 |
+
margin-right: 2px;
|
68 |
+
margin-left: 2px;
|
69 |
+
margin-top: 1px;
|
70 |
+
vertical-align: text-top;
|
71 |
+
}
|
72 |
+
|
73 |
+
/* The checkbox is only visible when viewing the menu configuration for a specific actor. */
|
74 |
+
#ws_menu_editor .ws_actor_access_checkbox {
|
75 |
+
display: none;
|
76 |
+
}
|
77 |
+
|
78 |
+
#ws_menu_editor.ws_is_actor_view .ws_actor_access_checkbox {
|
79 |
+
display: inline-block;
|
80 |
+
}
|
81 |
+
|
82 |
+
/* Gray-out items inaccessible to the currently selected actor */
|
83 |
+
|
84 |
+
.ws_is_actor_view .ws_container.ws_is_hidden_for_actor {
|
85 |
+
background-color: #F9F9F9;
|
86 |
+
}
|
87 |
+
|
88 |
+
.ws_is_actor_view .ws_is_hidden_for_actor .ws_item_title {
|
89 |
+
color: #777;
|
90 |
+
}
|
91 |
+
|
92 |
/*
|
93 |
* The sidebar
|
94 |
*/
|
98 |
padding: 2px;
|
99 |
}
|
100 |
|
101 |
+
#ws_menu_editor .ws_main_button {
|
102 |
clear: both;
|
103 |
display: block;
|
104 |
margin: 4px;
|
105 |
width: 120px;
|
106 |
}
|
107 |
|
108 |
+
#ws_menu_editor #ws_save_menu {
|
109 |
margin-bottom: 20px;
|
110 |
}
|
111 |
|
112 |
+
#ws_menu_editor #ws_export_menu {
|
113 |
margin-top: 12px;
|
114 |
}
|
115 |
|
122 |
width: 290px;
|
123 |
|
124 |
padding : 3px;
|
125 |
+
margin: 2px auto;
|
|
|
|
|
|
|
|
|
|
|
126 |
}
|
127 |
|
128 |
+
.ws_active { }
|
|
|
|
|
129 |
|
130 |
.ws_menu { }
|
131 |
.ws_item { }
|
132 |
|
133 |
+
.ws_menu_separator { }
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
.ws_submenu {
|
136 |
min-height: 2em;
|
142 |
}
|
143 |
|
144 |
.ws_item_title {
|
145 |
+
display: inline-block;
|
146 |
padding: 2px;
|
147 |
cursor: default;
|
148 |
}
|
149 |
|
150 |
.ws_edit_link {
|
151 |
float: right;
|
152 |
+
margin-right: 0;
|
153 |
cursor: pointer;
|
154 |
display:block;
|
155 |
width: 40px;
|
156 |
height: 22px;
|
157 |
|
|
|
|
|
|
|
|
|
158 |
border-radius: 3px;
|
159 |
-moz-border-radius: 3px;
|
160 |
-webkit-border-radius: 3px;
|
161 |
}
|
162 |
|
163 |
+
.ws_edit_link_expanded { }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
|
165 |
|
166 |
.ws_menu_drop_hover {
|
171 |
cursor: move !important;
|
172 |
}
|
173 |
|
174 |
+
/*
|
175 |
+
If you ever want to apply a right-arrow style to the currently selected menu item,
|
176 |
+
you can do it like this. Commented out for now since it doesn't look all that great,
|
177 |
+
but might be useful in the future.
|
178 |
+
*/
|
179 |
+
/*
|
180 |
+
.ws_container {
|
181 |
+
position: relative;
|
182 |
+
}
|
183 |
+
|
184 |
+
.ws_menu.ws_active::after {
|
185 |
+
content: "";
|
186 |
+
display: block;
|
187 |
+
z-index: 1002;
|
188 |
+
|
189 |
+
border-left: 14px solid #8EB0F1;
|
190 |
+
border-top: 14px solid transparent;
|
191 |
+
border-bottom: 14px solid transparent;
|
192 |
+
background: #8EB0F1;
|
193 |
+
|
194 |
+
position: absolute;
|
195 |
+
right: -14px;
|
196 |
+
top: -1px;
|
197 |
+
|
198 |
+
width: 0;
|
199 |
+
height: 0;
|
200 |
+
}
|
201 |
+
*/
|
202 |
+
|
203 |
/****************************************
|
204 |
Per-menu settings fields & panels
|
205 |
*****************************************/
|
206 |
|
207 |
.ws_editbox {
|
208 |
display: block;
|
|
|
209 |
padding: 4px;
|
210 |
+
|
211 |
border-radius: 2px;
|
212 |
+
border-top-right-radius: 0;
|
213 |
|
214 |
-moz-border-radius: 2px;
|
215 |
+
-moz-border-radius-topright: 0;
|
216 |
|
217 |
-webkit-border-radius: 2px;
|
218 |
+
-webkit-border-top-right-radius: 0;
|
219 |
}
|
220 |
|
221 |
.ws_edit_panel {
|
246 |
width: 16px;
|
247 |
height: 16px;
|
248 |
vertical-align: top;
|
249 |
+
|
250 |
+
background: url("../images/pencil_delete_gray.png") no-repeat center;
|
|
|
|
|
251 |
}
|
252 |
|
253 |
.ws_reset_button:hover {
|
258 |
color: gray;
|
259 |
}
|
260 |
|
261 |
+
/* No reset button for fields set to the default value and fields without a default value */
|
262 |
+
.ws_input_default .ws_reset_button,
|
263 |
+
.ws_has_no_default .ws_reset_button {
|
264 |
visibility: hidden;
|
265 |
}
|
266 |
|
281 |
}
|
282 |
|
283 |
#ws_menu_editor .ws_edit_field-custom input[type="checkbox"] {
|
284 |
+
margin-top: 0;
|
285 |
}
|
286 |
|
287 |
/* Dropdown button for combo-box fields */
|
288 |
+
#ws_menu_editor .ws_dropdown_button,
|
289 |
+
#ws_menu_access_editor .ws_dropdown_button
|
290 |
+
{
|
|
|
291 |
width: 20px;
|
292 |
+
height: 20px;
|
293 |
|
294 |
margin: 1px 1px 1px 0;
|
295 |
padding: 0;
|
302 |
|
303 |
border-top-right-radius: 3px;
|
304 |
border-bottom-right-radius: 3px;
|
305 |
+
border-top-left-radius: 0;
|
306 |
+
border-bottom-left-radius: 0;
|
307 |
|
308 |
-moz-border-radius-topright: 3px;
|
309 |
-moz-border-radius-bottomright: 3px;
|
310 |
+
-moz-border-radius-topleft: 0;
|
311 |
+
-moz-border-radius-bottomleft: 0;
|
312 |
|
313 |
-webkit-border-top-right-radius: 3px;
|
314 |
-webkit-border-bottom-right-radius: 3px;
|
315 |
+
-webkit-border-top-left-radius: 0;
|
316 |
+
-webkit-border-bottom-left-radius: 0;
|
317 |
+
}
|
318 |
+
|
319 |
+
#ws_menu_access_editor .ws_dropdown_button {
|
320 |
+
display: inline-block;
|
321 |
+
height: 22px;
|
322 |
+
margin-bottom: 2px;
|
323 |
+
}
|
324 |
+
|
325 |
+
#ws_menu_editor .ws_dropdown_button {
|
326 |
+
display: block;
|
327 |
+
float: left;
|
328 |
}
|
329 |
|
330 |
/*
|
331 |
+
The appearance and size of combo-box fields need to be changed
|
332 |
+
to accommodate the drop-down button.
|
333 |
*/
|
334 |
+
#ws_menu_editor .ws_has_dropdown input.ws_field_value,
|
335 |
+
#ws_menu_access_editor input.ws_has_dropdown
|
336 |
+
{
|
337 |
margin-right: 0;
|
338 |
border-right: 0;
|
339 |
|
340 |
+
border-top-right-radius: 0;
|
341 |
+
border-bottom-right-radius: 0;
|
342 |
+
|
343 |
+
-moz-border-radius-topright: 0;
|
344 |
+
-moz-border-radius-bottomright: 0;
|
345 |
+
|
346 |
+
-webkit-border-top-right-radius: 0;
|
347 |
+
-webkit-border-bottom-right-radius: 0;
|
348 |
+
}
|
349 |
|
350 |
+
#ws_menu_access_editor input.ws_has_dropdown {
|
351 |
+
width: 90%;
|
352 |
+
}
|
353 |
|
354 |
+
#ws_menu_editor .ws_has_dropdown input.ws_field_value {
|
355 |
+
width: 230px;
|
356 |
}
|
357 |
|
358 |
/* Unlike others, this field is just a single checkbox, so it has a smaller height */
|
403 |
}
|
404 |
|
405 |
/* user-created items */
|
406 |
+
.ws_custom_flag {
|
407 |
background-image: url('../images/page-add.png');
|
408 |
}
|
409 |
|
|
|
|
|
|
|
|
|
|
|
410 |
/* unused items - those that are in the default menu but not in the custom one */
|
411 |
.ws_unused_flag {
|
412 |
background-image: url('../images/plugin_add.png');
|
418 |
}
|
419 |
|
420 |
/* These classes could be used to apply different styles to items depending on their flags */
|
421 |
+
.ws_custom { }
|
|
|
422 |
.ws_hidden { }
|
423 |
.ws_unused { }
|
424 |
|
442 |
display: block;
|
443 |
margin-right: 3px;
|
444 |
padding: 4px;
|
|
|
445 |
float: left;
|
446 |
|
447 |
+
width: 16px;
|
448 |
+
height: 16px;
|
449 |
|
450 |
border-radius: 3px;
|
451 |
-moz-border-radius: 3px;
|
466 |
Capability selector
|
467 |
*************************************/
|
468 |
|
469 |
+
select.ws_dropdown {
|
470 |
width: 252px;
|
471 |
height: 20em;
|
472 |
|
478 |
font-size: 12px;
|
479 |
}
|
480 |
|
481 |
+
select.ws_dropdown option {
|
482 |
font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
|
483 |
font-size: 12px;
|
484 |
padding: 3px;
|
485 |
}
|
486 |
|
487 |
+
select.ws_dropdown optgroup option {
|
488 |
padding-left: 10px;
|
489 |
}
|
490 |
|
589 |
|
590 |
/* MP6 admin style compatibility */
|
591 |
#ws_icon_selector .ws_icon_option .icon16::before {
|
592 |
+
margin: 0;
|
593 |
+
padding: 0;
|
594 |
}
|
595 |
.ws_select_icon .icon16::before {
|
596 |
+
padding: 0;
|
597 |
+
margin: 1px 0 0 2px;
|
598 |
}
|
599 |
|
600 |
#ws_choose_icon_from_media {
|
612 |
.ui-widget-overlay {
|
613 |
background-color: black;
|
614 |
position: absolute;
|
615 |
+
left: 0;
|
616 |
+
top: 0;
|
617 |
opacity: 0.70;
|
618 |
-moz-opacity: 0.70;
|
619 |
filter: alpha(opacity=70);
|
651 |
}
|
652 |
|
653 |
.ui-dialog-titlebar-close {
|
654 |
+
background: #86A7E3 url(../images/x.png) no-repeat center;
|
|
|
|
|
|
|
|
|
655 |
width: 22px;
|
656 |
height: 22px;
|
657 |
display: block;
|
677 |
font-size: 1.1em;
|
678 |
}
|
679 |
|
680 |
+
#export_dialog .ws_dialog_panel {
|
681 |
+
height: 50px;
|
682 |
}
|
683 |
|
684 |
+
#import_dialog .ws_dialog_panel {
|
685 |
+
height: 64px;
|
686 |
}
|
687 |
|
688 |
.ws_dialog_buttons {
|
689 |
height: 23px;
|
690 |
text-align: right;
|
691 |
+
margin-top: 20px;
|
692 |
}
|
693 |
|
694 |
.ws_dialog_buttons .button-primary {
|
695 |
display: block;
|
696 |
float: left;
|
697 |
+
margin-top: 0;
|
698 |
}
|
699 |
|
700 |
.ws_dialog_buttons .button {
|
701 |
+
margin-top: 0;
|
702 |
}
|
703 |
|
704 |
#import_file_selector {
|
705 |
display: block;
|
706 |
width: 286px;
|
707 |
+
|
708 |
+
margin: 6px auto 12px;
|
|
|
|
|
|
|
709 |
}
|
710 |
|
711 |
#ws_start_import {
|
718 |
padding-top: 25px;
|
719 |
}
|
720 |
|
721 |
+
/************************************
|
722 |
+
Menu access editor
|
723 |
+
*************************************/
|
724 |
+
|
725 |
+
/* The launch button */
|
726 |
+
#ws_menu_editor .ws_edit_field-access_level input.ws_field_value
|
727 |
+
{
|
728 |
+
width: 190px;
|
729 |
+
margin-right: 5px;
|
730 |
+
}
|
731 |
+
|
732 |
+
.ws_launch_access_editor {
|
733 |
+
min-width: 40px;
|
734 |
+
}
|
735 |
+
|
736 |
+
#ws_menu_access_editor {
|
737 |
+
width: 400px;
|
738 |
+
display: none;
|
739 |
+
}
|
740 |
+
|
741 |
+
.ws_dialog_subpanel {
|
742 |
+
margin-bottom: 1em;
|
743 |
+
}
|
744 |
+
|
745 |
+
#ws_menu_access_editor .ws_column_access {
|
746 |
+
text-align: center;
|
747 |
+
width: 5em;
|
748 |
+
}
|
749 |
+
|
750 |
+
#ws_role_table_body_container {
|
751 |
+
max-height: 400px;
|
752 |
+
overflow: auto;
|
753 |
+
}
|
754 |
+
|
755 |
+
.ws_role_table_body {
|
756 |
+
margin-top: 2px;
|
757 |
+
}
|
758 |
+
|
759 |
+
.ws_has_separate_header .ws_role_table_header {
|
760 |
+
border-bottom: none;
|
761 |
+
|
762 |
+
-moz-border-radius-bottomleft: 0;
|
763 |
+
-moz-border-radius-bottomright: 0;
|
764 |
+
-webkit-border-bottom-left-radius: 0;
|
765 |
+
-webkit-border-bottom-right-radius: 0;
|
766 |
+
border-bottom-left-radius: 0;
|
767 |
+
border-bottom-right-radius: 0;
|
768 |
+
}
|
769 |
+
|
770 |
+
.ws_has_separate_header .ws_role_table_body {
|
771 |
+
border-top: none;
|
772 |
+
margin-top: 0;
|
773 |
+
|
774 |
+
-moz-border-radius-topleft: 0;
|
775 |
+
-moz-border-radius-topright: 0;
|
776 |
+
-webkit-border-top-left-radius: 0;
|
777 |
+
-webkit-border-top-right-radius: 0;
|
778 |
+
border-top-left-radius: 0;
|
779 |
+
border-top-right-radius: 0;
|
780 |
+
}
|
781 |
+
|
782 |
+
.ws_role_id {
|
783 |
+
display: none;
|
784 |
+
}
|
785 |
+
|
786 |
+
#ws_extra_capability {
|
787 |
+
width: 100%;
|
788 |
+
}
|
789 |
+
|
790 |
+
#ws_role_access_container {
|
791 |
+
position: relative;
|
792 |
+
}
|
793 |
+
|
794 |
+
#ws_role_access_overlay {
|
795 |
+
width: 100%;
|
796 |
+
height: 100%;
|
797 |
+
position: absolute;
|
798 |
+
|
799 |
+
line-height: 100%;
|
800 |
+
|
801 |
+
background: white;
|
802 |
+
filter: alpha(opacity=60);
|
803 |
+
opacity: 0.6;
|
804 |
+
-moz-opacity:0.6;
|
805 |
+
-khtml-opacity: 0.6;
|
806 |
+
}
|
807 |
+
|
808 |
+
#ws_role_access_overlay_content {
|
809 |
+
position: absolute;
|
810 |
+
width: 50%;
|
811 |
+
left: 22%;
|
812 |
+
top: 30%;
|
813 |
+
|
814 |
+
background: white;
|
815 |
+
padding: 8px;
|
816 |
+
|
817 |
+
border: 2px solid silver;
|
818 |
+
border-radius: 5px;
|
819 |
+
color: #555;
|
820 |
+
}
|
821 |
+
|
822 |
+
#ws_menu_access_editor div.error {
|
823 |
+
margin-left: 0;
|
824 |
+
margin-right: 0;
|
825 |
+
margin-bottom: 5px;
|
826 |
+
}
|
827 |
+
|
828 |
+
#ws_hardcoded_role_error {
|
829 |
+
display: none;
|
830 |
+
}
|
831 |
+
|
832 |
/************************************
|
833 |
Tooltips and hints
|
834 |
*************************************/
|
865 |
border-radius: 3px;
|
866 |
|
867 |
position: absolute;
|
868 |
+
right: 0;
|
869 |
+
top: 0;
|
870 |
}
|
871 |
|
872 |
.ws_hint_close:hover {
|
934 |
|
935 |
/* "Upgrade to Pro" */
|
936 |
#ws-pro-version-notice {
|
937 |
+
background: #00C31F none;
|
|
|
938 |
}
|
939 |
|
940 |
|
css/style-classic.css
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.ws_container {
|
2 |
+
border: 1px solid #a9badb;
|
3 |
+
background-color: #bdd3ff;
|
4 |
+
}
|
5 |
+
|
6 |
+
#ws_menu_editor .ws_active {
|
7 |
+
background-color : #8eb0f1; /* make sure this overrides ws_menu_separator */
|
8 |
+
}
|
9 |
+
|
10 |
+
#ws_menu_editor.ws_is_actor_view .ws_is_hidden_for_actor.ws_active {
|
11 |
+
background-color : #dadee6;
|
12 |
+
}
|
13 |
+
|
14 |
+
.ws_menu_separator {
|
15 |
+
background: #F9F9F9 url("../images/menu-arrows.png") no-repeat 4px 8px;
|
16 |
+
border-color: #d9d9d9;
|
17 |
+
}
|
18 |
+
|
19 |
+
.ws_edit_link {
|
20 |
+
background-image: url('../images/bullet_arrow_down2.png');
|
21 |
+
background-repeat: no-repeat;
|
22 |
+
background-position: center;
|
23 |
+
}
|
24 |
+
|
25 |
+
a.ws_edit_link:hover {
|
26 |
+
background-color: #ffffd0;
|
27 |
+
background-image: url('../images/bullet_arrow_down2.png');
|
28 |
+
}
|
29 |
+
|
30 |
+
.ws_edit_link:active {
|
31 |
+
background-repeat: no-repeat;
|
32 |
+
background-position: center;
|
33 |
+
}
|
34 |
+
|
35 |
+
.ws_edit_link_expanded {
|
36 |
+
background: #ffffd0 url('../images/bullet_arrow_down2.png') center 3px no-repeat;
|
37 |
+
border-bottom: none;
|
38 |
+
border-color: #ffffd0;
|
39 |
+
|
40 |
+
padding-bottom: 1px;
|
41 |
+
border-bottom-right-radius: 0;
|
42 |
+
border-bottom-left-radius: 0;
|
43 |
+
|
44 |
+
-moz-border-radius-bottomright: 0;
|
45 |
+
-moz-border-radius-bottomleft: 0;
|
46 |
+
|
47 |
+
-webkit-border-bottom-right-radius: 0;
|
48 |
+
-webkit-border-bottom-left-radius: 0;
|
49 |
+
}
|
50 |
+
|
51 |
+
|
52 |
+
.ws_editbox {
|
53 |
+
background-color: #ffffd0;
|
54 |
+
}
|
55 |
+
|
56 |
+
.ws_input_default input, .ws_input_default select {
|
57 |
+
color: gray;
|
58 |
+
}
|
59 |
+
|
60 |
+
/*
|
61 |
+
* Show/Hide advanced fields
|
62 |
+
*/
|
63 |
+
|
64 |
+
.ws_toggle_advanced_fields {
|
65 |
+
color: #6087CB;
|
66 |
+
font-size: 0.85em;
|
67 |
+
}
|
68 |
+
|
69 |
+
.ws_toggle_advanced_fields:visited, .ws_toggle_advanced_fields:active {
|
70 |
+
color: #6087CB;
|
71 |
+
}
|
72 |
+
|
73 |
+
.ws_toggle_advanced_fields:hover {
|
74 |
+
color: #d54e21;
|
75 |
+
text-decoration: underline;
|
76 |
+
}
|
77 |
+
|
78 |
+
|
79 |
+
/*
|
80 |
+
* Toolbars
|
81 |
+
*/
|
82 |
+
|
83 |
+
.ws_button {
|
84 |
+
border: 1px solid #c0c0e0;
|
85 |
+
}
|
86 |
+
|
87 |
+
a.ws_button:hover {
|
88 |
+
background-color: #d0e0ff;
|
89 |
+
border-color: #9090c0;
|
90 |
+
}
|
91 |
+
|
92 |
+
/************************************
|
93 |
+
Export and import
|
94 |
+
*************************************/
|
95 |
+
|
96 |
+
.ui-dialog {
|
97 |
+
background: white;
|
98 |
+
border: 1px solid #c0c0c0;
|
99 |
+
}
|
100 |
+
|
101 |
+
.ui-dialog-titlebar {
|
102 |
+
background-color: #86A7E3;
|
103 |
+
}
|
104 |
+
|
105 |
+
.ui-dialog-title {
|
106 |
+
color: white;
|
107 |
+
}
|
108 |
+
|
109 |
+
.ui-dialog-titlebar-close {
|
110 |
+
background: #86A7E3 url(../images/x.png) no-repeat center;
|
111 |
+
color: white;
|
112 |
+
}
|
113 |
+
|
114 |
+
.ui-dialog-titlebar-close:hover {
|
115 |
+
/*background-image: url(../images/x-light.png);*/
|
116 |
+
background-color: #a6c2f5;
|
117 |
+
}
|
css/style-wp-gray.css
ADDED
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.ws_container {
|
2 |
+
padding: 0;
|
3 |
+
width: 296px;
|
4 |
+
margin-bottom: 5px;
|
5 |
+
|
6 |
+
background: white;
|
7 |
+
|
8 |
+
border: 1px solid #aeaeae;
|
9 |
+
|
10 |
+
-webkit-box-shadow: inset 0 1px 0 #fff;
|
11 |
+
box-shadow: inset 0 1px 0 #fff;
|
12 |
+
|
13 |
+
-webkit-border-radius: 3px;
|
14 |
+
border-radius: 3px;
|
15 |
+
}
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Item head elements
|
19 |
+
*/
|
20 |
+
|
21 |
+
.ws_item_head {
|
22 |
+
padding: 0;
|
23 |
+
border-top-left-radius: 3px;
|
24 |
+
border-top-right-radius: 3px;
|
25 |
+
|
26 |
+
background-color: #d9d9d9;
|
27 |
+
background-image: -o-linear-gradient(top, #efefef, #d9d9d9);
|
28 |
+
background-image: -o-linear-gradient(top, #e9e9e9, #d9d9d9);
|
29 |
+
background-image: -ms-linear-gradient(top, #e9e9e9, #d9d9d9);
|
30 |
+
background-image: -moz-linear-gradient(top, #e9e9e9, #d9d9d9);
|
31 |
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#e9e9e9), to(#d9d9d9));
|
32 |
+
background-image: -webkit-linear-gradient(top, #e9e9e9, #d9d9d9);
|
33 |
+
background-image: linear-gradient(top, #e9e9e9, #d9d9d9);
|
34 |
+
/*background-color: #c7c7c7;*/
|
35 |
+
}
|
36 |
+
|
37 |
+
.ws_item_title {
|
38 |
+
padding: 6px 5px;
|
39 |
+
|
40 |
+
/*font-weight: bold;*/
|
41 |
+
color: #222;
|
42 |
+
text-shadow: #FFFFFF 0px 1px 0px;
|
43 |
+
}
|
44 |
+
|
45 |
+
.ws_flag_container {
|
46 |
+
padding-top: 6px;
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* The down-arrow that expands menu settings
|
51 |
+
*/
|
52 |
+
|
53 |
+
.ws_edit_link {
|
54 |
+
width: 30px;
|
55 |
+
height: 30px;
|
56 |
+
|
57 |
+
background: transparent url(../images/arrows.png) no-repeat center 5px;
|
58 |
+
overflow: hidden;
|
59 |
+
text-indent:-999em;
|
60 |
+
}
|
61 |
+
|
62 |
+
a.ws_edit_link:hover {
|
63 |
+
background-image: url(../images/arrows-dark.png);
|
64 |
+
}
|
65 |
+
|
66 |
+
.ws_edit_link:active {
|
67 |
+
background: transparent url(../images/arrows-dark.png) no-repeat center 5px;
|
68 |
+
}
|
69 |
+
|
70 |
+
.ws_edit_link_expanded {
|
71 |
+
border-bottom: none;
|
72 |
+
border-color: #ffffd0;
|
73 |
+
|
74 |
+
padding-bottom: 1px;
|
75 |
+
border-bottom-right-radius: 0;
|
76 |
+
border-bottom-left-radius: 0;
|
77 |
+
|
78 |
+
-moz-border-radius-bottomright: 0;
|
79 |
+
-moz-border-radius-bottomleft: 0;
|
80 |
+
|
81 |
+
-webkit-border-bottom-right-radius: 0;
|
82 |
+
-webkit-border-bottom-left-radius: 0;
|
83 |
+
}
|
84 |
+
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Separators
|
88 |
+
*/
|
89 |
+
|
90 |
+
.ws_menu_separator {
|
91 |
+
border-color: #d9d9d9;
|
92 |
+
}
|
93 |
+
|
94 |
+
.ws_menu_separator .ws_item_head {
|
95 |
+
height: 29px;
|
96 |
+
border-radius: 3px;
|
97 |
+
background: #F9F9F9 url("../images/menu-arrows.png") no-repeat 4px 8px;
|
98 |
+
}
|
99 |
+
|
100 |
+
.ws_menu_separator .ws_item_title {
|
101 |
+
display: none;
|
102 |
+
}
|
103 |
+
|
104 |
+
.ws_menu_separator.ws_active .ws_item_head {
|
105 |
+
background: #999 url("../images/menu-arrows.png") no-repeat 4px 8px;
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Active item
|
110 |
+
*/
|
111 |
+
|
112 |
+
.ws_active .ws_item_head {
|
113 |
+
background: #777;
|
114 |
+
background-image: -webkit-gradient(linear, left bottom, left top, from(#6d6d6d), to(#808080));
|
115 |
+
background-image: -webkit-linear-gradient(bottom, #6d6d6d, #808080);
|
116 |
+
background-image: -moz-linear-gradient(bottom, #6d6d6d, #808080);
|
117 |
+
background-image: -o-linear-gradient(bottom, #6d6d6d, #808080);
|
118 |
+
background-image: linear-gradient(to top, #6d6d6d, #808080);
|
119 |
+
}
|
120 |
+
|
121 |
+
.ws_active .ws_item_title {
|
122 |
+
text-shadow: 0 -1px 0 #333;
|
123 |
+
color: #fff;
|
124 |
+
border-top-color: #808080;
|
125 |
+
border-bottom-color: #6d6d6d;
|
126 |
+
}
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Dropping menus on other menus.
|
130 |
+
*/
|
131 |
+
|
132 |
+
.ws_menu_drop_hover, .ws_menu_drop_hover .ws_item_head {
|
133 |
+
background: #43b529;
|
134 |
+
}
|
135 |
+
|
136 |
+
.ws_menu_drop_hover .ws_item_title {
|
137 |
+
text-shadow: none;
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Misc
|
142 |
+
*/
|
143 |
+
|
144 |
+
.ws_editbox {
|
145 |
+
/*background-color: #ffffd0;*/
|
146 |
+
background-color: #FBFBFB;
|
147 |
+
padding: 4px 6px;
|
148 |
+
}
|
149 |
+
|
150 |
+
.ws_input_default input, .ws_input_default select {
|
151 |
+
color: gray;
|
152 |
+
}
|
153 |
+
|
154 |
+
/*
|
155 |
+
* Show/Hide advanced fields
|
156 |
+
*/
|
157 |
+
|
158 |
+
.ws_toggle_advanced_fields {
|
159 |
+
color: #6087CB;
|
160 |
+
font-size: 0.85em;
|
161 |
+
}
|
162 |
+
|
163 |
+
.ws_toggle_advanced_fields:visited, .ws_toggle_advanced_fields:active {
|
164 |
+
color: #6087CB;
|
165 |
+
}
|
166 |
+
|
167 |
+
.ws_toggle_advanced_fields:hover {
|
168 |
+
color: #d54e21;
|
169 |
+
text-decoration: underline;
|
170 |
+
}
|
171 |
+
|
172 |
+
|
173 |
+
/*
|
174 |
+
* Toolbars
|
175 |
+
*/
|
176 |
+
|
177 |
+
.ws_button {
|
178 |
+
border: 1px solid #c0c0e0;
|
179 |
+
}
|
180 |
+
|
181 |
+
a.ws_button:hover {
|
182 |
+
background-color: #d0e0ff;
|
183 |
+
border-color: #9090c0;
|
184 |
+
}
|
185 |
+
|
186 |
+
/************************************
|
187 |
+
Export and import
|
188 |
+
*************************************/
|
189 |
+
|
190 |
+
.ui-dialog {
|
191 |
+
background: white;
|
192 |
+
border: 1px solid #c0c0c0;
|
193 |
+
}
|
194 |
+
|
195 |
+
.ui-dialog-titlebar {
|
196 |
+
background-color: #86A7E3;
|
197 |
+
}
|
198 |
+
|
199 |
+
.ui-dialog-title {
|
200 |
+
color: white;
|
201 |
+
}
|
202 |
+
|
203 |
+
.ui-dialog-titlebar-close {
|
204 |
+
background: #86A7E3 url(../images/x.png) no-repeat center;
|
205 |
+
color: white;
|
206 |
+
}
|
207 |
+
|
208 |
+
.ui-dialog-titlebar-close:hover {
|
209 |
+
/*background-image: url(../images/x-light.png);*/
|
210 |
+
background-color: #a6c2f5;
|
211 |
+
}
|
images/arrows-dark.png
ADDED
Binary file
|
images/arrows.png
ADDED
Binary file
|
images/check-all.png
ADDED
Binary file
|
images/external.png
ADDED
Binary file
|
images/logo-medium.png
ADDED
Binary file
|
images/plugin_disabled.png
ADDED
Binary file
|
includes/access-editor-dialog.php
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div id="ws_menu_access_editor" title="Permissions">
|
2 |
+
|
3 |
+
<div class="ws_dialog_panel">
|
4 |
+
<div class="ws_hint" id="ws_hint_menu_permissions">
|
5 |
+
<div class="ws_hint_close" title="Close">x</div>
|
6 |
+
<div class="ws_hint_content">
|
7 |
+
<strong>Hint:</strong> Individual submenus can override these settings.
|
8 |
+
As a result, this menu will stay visible as long as at least one of its
|
9 |
+
submenus is accessible.
|
10 |
+
</div>
|
11 |
+
</div>
|
12 |
+
|
13 |
+
<div class="error inline" id="ws_hardcoded_role_error">
|
14 |
+
<p>
|
15 |
+
<strong>Note:</strong>
|
16 |
+
Only users with the "<span id="ws_hardcoded_role_name">[role]</span>" role
|
17 |
+
can access this menu. This restriction is hard­coded in the plugin that
|
18 |
+
created the menu.
|
19 |
+
</p>
|
20 |
+
</div>
|
21 |
+
|
22 |
+
<div id="ws_role_access_container" class="ws_dialog_subpanel">
|
23 |
+
<strong>Grant access</strong>
|
24 |
+
<a class="ws_tooltip_trigger" title="
|
25 |
+
Check a box to give that role or user the required capability and access to this menu.
|
26 |
+
Clear the box to prevent access.
|
27 |
+
">[?]</a>
|
28 |
+
<br>
|
29 |
+
|
30 |
+
<div id="ws_role_table_body_container">
|
31 |
+
<div id="ws_role_access_overlay" class="ws_hide_if_pro"></div>
|
32 |
+
<div id="ws_role_access_overlay_content" class="ws_hide_if_pro">
|
33 |
+
Pro only feature.
|
34 |
+
Use capabilities (below) instead.
|
35 |
+
</div>
|
36 |
+
|
37 |
+
<table class="widefat fixed ws_role_table_body">
|
38 |
+
<tbody>
|
39 |
+
<!-- Table contents will be generated by JavaScript. -->
|
40 |
+
</tbody>
|
41 |
+
</table>
|
42 |
+
</div>
|
43 |
+
</div>
|
44 |
+
|
45 |
+
|
46 |
+
<div id="ws_required_cap_container" class="ws_dialog_subpanel">
|
47 |
+
<strong>Required capability</strong>
|
48 |
+
<a class="ws_tooltip_trigger" title="
|
49 |
+
This capability check is hard-coded in WordPress or the plugin that created the menu.
|
50 |
+
|
51 |
+
<ul class="ws_tooltip_content_list">
|
52 |
+
<li>
|
53 |
+
Only roles with the this capability will be able to access this menu.
|
54 |
+
<li>
|
55 |
+
Admin Menu Editor will automatically grant the required capability to
|
56 |
+
all roles you check in the "Roles" list.
|
57 |
+
<li>
|
58 |
+
Custom menus have no hard-coded capability requirements.
|
59 |
+
</ul>
|
60 |
+
">[?]</a>
|
61 |
+
<br>
|
62 |
+
<span id="ws_required_capability">capability_here</span>
|
63 |
+
</div>
|
64 |
+
|
65 |
+
<div id="ws_extra_cap_container" class="ws_dialog_subpanel">
|
66 |
+
<label for="ws_extra_capability">
|
67 |
+
<strong>Extra capability</strong>
|
68 |
+
</label>
|
69 |
+
<a class="ws_tooltip_trigger" title="
|
70 |
+
Optional. An additional capability check that will be applied on top of
|
71 |
+
the "Roles" and "Required capability" settings.
|
72 |
+
Leave empty to disable.
|
73 |
+
">[?]</a>
|
74 |
+
<br>
|
75 |
+
<input type="text" id="ws_extra_capability" class="ws_has_dropdown" value=""><input type="button" id="ws_trigger_capability_dropdown" value="▼"
|
76 |
+
class="button ws_dropdown_button" tabindex="-1">
|
77 |
+
</div>
|
78 |
+
</div>
|
79 |
+
|
80 |
+
<div class="ws_dialog_buttons">
|
81 |
+
<input type="button" class="button-primary" value="Save Changes" id="ws_save_access_settings">
|
82 |
+
<input type="button" class="button ws_close_dialog" value="Cancel">
|
83 |
+
</div>
|
84 |
+
|
85 |
+
</div>
|
includes/admin-menu-editor-mu.php
CHANGED
@@ -1,4 +1,11 @@
|
|
1 |
<?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
/**
|
4 |
To install Admin Menu Editor as a global plugin in WPMU :
|
@@ -29,7 +36,7 @@ if ( file_exists($ws_menu_editor_filename) ) {
|
|
29 |
}
|
30 |
|
31 |
function ws_ame_installation_error(){
|
32 |
-
if ( !
|
33 |
?>
|
34 |
<div class="error fade"><p>
|
35 |
<strong>Admin Menu Editor is installed incorrectly!</strong>
|
@@ -42,5 +49,3 @@ function ws_ame_installation_error(){
|
|
42 |
</div>
|
43 |
<?php
|
44 |
}
|
45 |
-
|
46 |
-
?>
|
1 |
<?php
|
2 |
+
/*
|
3 |
+
Plugin Name: Admin Menu Editor Pro (Multisite module)
|
4 |
+
Plugin URI: http://w-shadow.com/admin-menu-editor-pro/
|
5 |
+
Description: Lets you edit the WordPress admin menu. To access the editor, go to the Dashboard of one of your network sites and open the Settings -> Menu Editor page.
|
6 |
+
Author: Janis Elsts
|
7 |
+
Author URI: http://w-shadow.com/
|
8 |
+
*/
|
9 |
|
10 |
/**
|
11 |
To install Admin Menu Editor as a global plugin in WPMU :
|
36 |
}
|
37 |
|
38 |
function ws_ame_installation_error(){
|
39 |
+
if ( !is_super_admin() ) return;
|
40 |
?>
|
41 |
<div class="error fade"><p>
|
42 |
<strong>Admin Menu Editor is installed incorrectly!</strong>
|
49 |
</div>
|
50 |
<?php
|
51 |
}
|
|
|
|
includes/auto-versioning.php
ADDED
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('AutoVersioning') ) {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* This class enables automatic versioning of CSS/JS by adding file modification time to the URLs.
|
7 |
+
* @see http://stackoverflow.com/questions/118884/
|
8 |
+
*/
|
9 |
+
class AutoVersioning {
|
10 |
+
private static $version_in_filename = false;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* An auto-versioning wrapper for wp_register_s*() and wp_enqueue_s*() dependency APIs.
|
14 |
+
*
|
15 |
+
* @static
|
16 |
+
* @param string $wp_api_function The name of the WP dependency API to call.
|
17 |
+
* @param string $handle Script or stylesheet handle.
|
18 |
+
* @param string $src Script or stylesheet URL.
|
19 |
+
* @param array $deps Dependencies.
|
20 |
+
* @param bool|string $last_param Either $media (for wp_register_style) or $in_footer (for wp_register_script).
|
21 |
+
* @param bool $add_ver_to_filename TRUE = add version to filename, FALSE = add it to the query string.
|
22 |
+
*/
|
23 |
+
public static function add_dependency($wp_api_function, $handle, $src, $deps, $last_param, $add_ver_to_filename = false ) {
|
24 |
+
list($src, $version) = self::auto_version($src, $add_ver_to_filename);
|
25 |
+
call_user_func($wp_api_function, $handle, $src, $deps, $version, $last_param);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Automatically version a script or style sheet URL based on file modification time.
|
30 |
+
*
|
31 |
+
* Returns auto-versioned $src and $ver values suitable for use with WordPress dependency APIs like
|
32 |
+
* wp_register_script() and wp_register_style().
|
33 |
+
*
|
34 |
+
* @static
|
35 |
+
* @param string $url
|
36 |
+
* @param bool $add_ver_to_filename
|
37 |
+
* @return array array($url, $version)
|
38 |
+
*/
|
39 |
+
private static function auto_version($url, $add_ver_to_filename = false) {
|
40 |
+
$version = false;
|
41 |
+
$filename = self::guess_filename_from_url($url);
|
42 |
+
|
43 |
+
if ( ($filename !== null) && is_file($filename) ) {
|
44 |
+
$mtime = filemtime($filename);
|
45 |
+
if ( $add_ver_to_filename ) {
|
46 |
+
$url = preg_replace('@\.([^./\?]+)(\?.*)?$@', '.' . $mtime . '.$1', $url);
|
47 |
+
$version = null;
|
48 |
+
} else {
|
49 |
+
$version = $mtime;
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
return array($url, $version);
|
54 |
+
}
|
55 |
+
|
56 |
+
private static function guess_filename_from_url($url) {
|
57 |
+
$url_mappings = array(
|
58 |
+
plugins_url() => WP_PLUGIN_DIR,
|
59 |
+
plugins_url('', WPMU_PLUGIN_DIR . '/dummy') => WPMU_PLUGIN_DIR,
|
60 |
+
get_stylesheet_directory_uri() => get_stylesheet_directory(),
|
61 |
+
get_template_directory_uri() => get_template_directory(),
|
62 |
+
content_url() => WP_CONTENT_DIR,
|
63 |
+
site_url('/' . WPINC) => ABSPATH . WPINC,
|
64 |
+
);
|
65 |
+
|
66 |
+
$filename = null;
|
67 |
+
foreach($url_mappings as $root_url => $directory) {
|
68 |
+
if ( strpos($url, $root_url) === 0 ) {
|
69 |
+
$filename = $directory . '/' . substr($url, strlen($root_url));
|
70 |
+
//Get rid of the query string, if any.
|
71 |
+
list($filename, ) = explode('?', $filename, 2);
|
72 |
+
break;
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
return $filename;
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Apply automatic versioning to all scripts and style sheets added using WP dependency APIs.
|
81 |
+
*
|
82 |
+
* If you set $add_ver_to_filename to TRUE, make sure to also add the following code to your
|
83 |
+
* .htaccess file or your site may break:
|
84 |
+
*
|
85 |
+
* <IfModule mod_rewrite.c>
|
86 |
+
* RewriteEngine On
|
87 |
+
* RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]
|
88 |
+
* </IfModule>
|
89 |
+
*
|
90 |
+
* @static
|
91 |
+
* @param bool $add_ver_to_filename
|
92 |
+
*/
|
93 |
+
public static function apply_to_all_dependencies($add_ver_to_filename = false) {
|
94 |
+
self::$version_in_filename = $add_ver_to_filename;
|
95 |
+
foreach(array('script_loader_src', 'style_loader_src') as $hook) {
|
96 |
+
add_filter($hook, __CLASS__ . '::_filter_dependency_src', 10, 1);
|
97 |
+
}
|
98 |
+
}
|
99 |
+
|
100 |
+
public static function _filter_dependency_src($src) {
|
101 |
+
//Only add version info to CSS/JS files that don't already have it in the file name.
|
102 |
+
if ( preg_match('@(?<!\.\d{10})\.(css|js)(\?|$)@i', $src) ) {
|
103 |
+
list($src, $version) = self::auto_version($src, self::$version_in_filename);
|
104 |
+
if ( !empty($version) ) {
|
105 |
+
$src = add_query_arg('ver', $version, $src);
|
106 |
+
}
|
107 |
+
}
|
108 |
+
return $src;
|
109 |
+
}
|
110 |
+
}
|
111 |
+
|
112 |
+
} //class_exists()
|
113 |
+
|
114 |
+
if ( !function_exists('wp_register_auto_versioned_script') ) {
|
115 |
+
function wp_register_auto_versioned_script($handle, $src, $deps = array(), $in_footer = false, $add_ver_to_filename = false) {
|
116 |
+
AutoVersioning::add_dependency('wp_register_script', $handle, $src, $deps, $in_footer, $add_ver_to_filename);
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
if ( !function_exists('wp_register_auto_versioned_style') ) {
|
121 |
+
function wp_register_auto_versioned_style( $handle, $src, $deps = array(), $media = 'all', $add_ver_to_filename = false) {
|
122 |
+
AutoVersioning::add_dependency('wp_register_style', $handle, $src, $deps, $media, $add_ver_to_filename);
|
123 |
+
}
|
124 |
+
}
|
125 |
+
|
126 |
+
if ( !function_exists('wp_enqueue_auto_versioned_script') ) {
|
127 |
+
function wp_enqueue_auto_versioned_script( $handle, $src, $deps = array(), $in_footer = false, $add_ver_to_filename = false ) {
|
128 |
+
AutoVersioning::add_dependency('wp_enqueue_script', $handle, $src, $deps, $in_footer, $add_ver_to_filename);
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
132 |
+
if ( !function_exists('wp_enqueue_auto_versioned_style') ) {
|
133 |
+
function wp_enqueue_auto_versioned_style( $handle, $src, $deps = array(), $media = 'all', $add_ver_to_filename = false ) {
|
134 |
+
AutoVersioning::add_dependency('wp_enqueue_style', $handle, $src, $deps, $media, $add_ver_to_filename);
|
135 |
+
}
|
136 |
+
}
|
includes/consistency-check.php
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !defined('ABSPATH') ) {
|
3 |
+
die();
|
4 |
+
}
|
5 |
+
|
6 |
+
/** @var string $pluginFile Should be provided by the including file. */
|
7 |
+
|
8 |
+
$log = array();
|
9 |
+
$log[] = sprintf(
|
10 |
+
'[OK] Main plugin file: %s',
|
11 |
+
$pluginFile
|
12 |
+
);
|
13 |
+
|
14 |
+
$log[] = sprintf(
|
15 |
+
'[Info] WordPress version: %s',
|
16 |
+
$GLOBALS['wp_version']
|
17 |
+
);
|
18 |
+
|
19 |
+
$log[] = sprintf(
|
20 |
+
'[Info] WP_PLUGIN_DIR: %s',
|
21 |
+
WP_PLUGIN_DIR
|
22 |
+
);
|
23 |
+
|
24 |
+
$log[] = sprintf(
|
25 |
+
'[Info] WP_PLUGIN_URL: %s',
|
26 |
+
WP_PLUGIN_URL
|
27 |
+
);
|
28 |
+
|
29 |
+
$log[] = sprintf(
|
30 |
+
'[Info] WPMU_PLUGIN_DIR: %s',
|
31 |
+
WPMU_PLUGIN_DIR
|
32 |
+
);
|
33 |
+
|
34 |
+
$log[] = sprintf(
|
35 |
+
'[Info] WPMU_PLUGIN_URL: %s',
|
36 |
+
WPMU_PLUGIN_URL
|
37 |
+
);
|
38 |
+
|
39 |
+
$expectedPluginRoot = dirname(dirname(__FILE__));
|
40 |
+
$actualPluginRoot = dirname($pluginFile);
|
41 |
+
|
42 |
+
if ( $expectedPluginRoot === $actualPluginRoot ) {
|
43 |
+
$log[] = sprintf(
|
44 |
+
'[OK] Plugin root directory is "%s"',
|
45 |
+
$actualPluginRoot
|
46 |
+
);
|
47 |
+
} else {
|
48 |
+
$log[] = sprintf(
|
49 |
+
'[Error] Actual plugin directory: "%s", expected: "%s"',
|
50 |
+
$actualPluginRoot,
|
51 |
+
$expectedPluginRoot
|
52 |
+
);
|
53 |
+
}
|
54 |
+
|
55 |
+
$requiredFiles = array(
|
56 |
+
'css/menu-editor.css',
|
57 |
+
'css/jquery.qtip.min.css',
|
58 |
+
'js/menu-editor.js',
|
59 |
+
'js/menu-highlight-fix.js',
|
60 |
+
'js/jquery.sort.js',
|
61 |
+
'js/jquery.qtip.min.js',
|
62 |
+
'js/jquery.json.js',
|
63 |
+
'images/cut.png',
|
64 |
+
'images/delete.png',
|
65 |
+
'images/page_white_add.png',
|
66 |
+
'images/spinner.gif',
|
67 |
+
'includes/editor-page.php',
|
68 |
+
'includes/menu-editor-core.php',
|
69 |
+
'includes/access-editor-dialog.php',
|
70 |
+
'includes/menu-item.php',
|
71 |
+
'menu-editor.php',
|
72 |
+
'uninstall.php',
|
73 |
+
);
|
74 |
+
|
75 |
+
foreach($requiredFiles as $filename) {
|
76 |
+
$fullPath = dirname($pluginFile) . '/' . $filename;
|
77 |
+
if ( is_readable($fullPath) ) {
|
78 |
+
$log[] = sprintf(
|
79 |
+
'[OK] File exists: %s',
|
80 |
+
$fullPath
|
81 |
+
);
|
82 |
+
} else {
|
83 |
+
$log[] = sprintf(
|
84 |
+
'[Error] File does not exist: %s',
|
85 |
+
$fullPath
|
86 |
+
);
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
foreach($requiredFiles as $filename) {
|
91 |
+
if ( !preg_match('@\.(css|js|png)$@', $filename) ) {
|
92 |
+
continue;
|
93 |
+
}
|
94 |
+
|
95 |
+
$url = plugins_url($filename, $pluginFile);
|
96 |
+
$log[] = ame_test_url_access($url, $filename);
|
97 |
+
}
|
98 |
+
|
99 |
+
echo '<pre>';
|
100 |
+
$divider = str_repeat('-', 50);
|
101 |
+
echo "File consistency checks:\n", $divider, "\n";
|
102 |
+
foreach($log as $message) {
|
103 |
+
echo $message, "\n";
|
104 |
+
}
|
105 |
+
|
106 |
+
//Test for buggy plugins_url filters.
|
107 |
+
echo $divider, "\nTesting for problems with the 'plugins_url' hook...\n";
|
108 |
+
add_filter('plugins_url', 'ame_plugins_url_test_first', -9999, 3);
|
109 |
+
add_filter('plugins_url', 'ame_plugins_url_test_last', 9999, 3);
|
110 |
+
|
111 |
+
$url = plugins_url('css/menu-editor.css', $pluginFile);
|
112 |
+
|
113 |
+
remove_filter('plugins_url', 'ame_plugins_url_test_first', -9999, 3);
|
114 |
+
remove_filter('plugins_url', 'ame_plugins_url_test_last', 9999, 3);
|
115 |
+
|
116 |
+
function ame_plugins_url_test_first($url, $path = '', $plugin = '') {
|
117 |
+
printf(
|
118 |
+
'[Info] plugins_url() output before plugin hooks: %s' . "\n",
|
119 |
+
esc_html($url)
|
120 |
+
);
|
121 |
+
echo ame_test_url_access($url, 'css/menu-editor.css'), "\n";
|
122 |
+
return $url;
|
123 |
+
}
|
124 |
+
|
125 |
+
function ame_plugins_url_test_last($url, $path = '', $plugin = '') {
|
126 |
+
printf(
|
127 |
+
'[Info] plugins_url() output after plugin hooks: %s' . "\n",
|
128 |
+
esc_html($url)
|
129 |
+
);
|
130 |
+
echo ame_test_url_access($url, 'css/menu-editor.css'), "\n";
|
131 |
+
return $url;
|
132 |
+
}
|
133 |
+
|
134 |
+
function ame_test_url_access($url, $filename) {
|
135 |
+
$result = wp_remote_get($url);
|
136 |
+
|
137 |
+
if ( is_wp_error($result) ) {
|
138 |
+
return sprintf(
|
139 |
+
'[Error] Can not load URL: %s (%s)',
|
140 |
+
esc_html($url),
|
141 |
+
$result->get_error_message()
|
142 |
+
);
|
143 |
+
} else if ( $result['response']['code'] == 200 ) {
|
144 |
+
return sprintf(
|
145 |
+
'[OK] URL is accessible: %s',
|
146 |
+
esc_html($url)
|
147 |
+
);
|
148 |
+
} else {
|
149 |
+
return sprintf(
|
150 |
+
'[Error] Can no load "%s", URL : %s (%d %s)',
|
151 |
+
esc_html($filename),
|
152 |
+
esc_html($url),
|
153 |
+
$result['response']['code'],
|
154 |
+
$result['response']['message']
|
155 |
+
);
|
156 |
+
}
|
157 |
+
}
|
158 |
+
|
159 |
+
echo $divider;
|
160 |
+
echo '</pre>';
|
includes/editor-page.php
ADDED
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @var array $editor_data Various pieces of data passed by the plugin.
|
4 |
+
*/
|
5 |
+
$images_url = $editor_data['images_url'];
|
6 |
+
|
7 |
+
$icons = array(
|
8 |
+
'cut' => '/gnome-icon-theme/edit-cut-blue.png',
|
9 |
+
'copy' => '/gion/edit-copy.png',
|
10 |
+
'paste' => '/gnome-icon-theme/edit-paste.png',
|
11 |
+
'hide' => '/icon-extension-grey.png',
|
12 |
+
'new' => '/page-add.png',
|
13 |
+
'delete' => '/page-delete.png',
|
14 |
+
'new-separator' => '/separator-add.png',
|
15 |
+
'toggle-all' => '/check-all.png',
|
16 |
+
);
|
17 |
+
foreach($icons as $name => $url) {
|
18 |
+
$icons[$name] = $images_url . $url;
|
19 |
+
}
|
20 |
+
|
21 |
+
//Output the "Upgrade to Pro" message
|
22 |
+
if ( !apply_filters('admin_menu_editor_is_pro', false) ){
|
23 |
+
?>
|
24 |
+
<script type="text/javascript">
|
25 |
+
(function($){
|
26 |
+
$('#screen-meta-links').append(
|
27 |
+
'<div id="ws-pro-version-notice">' +
|
28 |
+
'<a href="http://adminmenueditor.com/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=top_upgrade_link&utm_campaign=Plugins" id="ws-pro-version-notice-link" class="show-settings" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
|
29 |
+
'</div>'
|
30 |
+
);
|
31 |
+
})(jQuery);
|
32 |
+
</script>
|
33 |
+
<?php
|
34 |
+
}
|
35 |
+
|
36 |
+
?>
|
37 |
+
<div class="wrap">
|
38 |
+
<h2>
|
39 |
+
<?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
|
40 |
+
<a href="<?php echo esc_attr($editor_data['settings_page_url']); ?>" class="add-new-h2" id="ws_plugin_settings_button"
|
41 |
+
title="Configure plugin settings">Settings</a>
|
42 |
+
</h2>
|
43 |
+
|
44 |
+
<?php
|
45 |
+
if ( !empty($_GET['message']) ){
|
46 |
+
if ( intval($_GET['message']) == 1 ){
|
47 |
+
echo '<div id="message" class="updated fade"><p><strong>Settings saved.</strong></p></div>';
|
48 |
+
} elseif ( intval($_GET['message']) == 2 ) {
|
49 |
+
echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
|
50 |
+
}
|
51 |
+
}
|
52 |
+
?>
|
53 |
+
|
54 |
+
<?php
|
55 |
+
$hint_id = 'ws_whats_new_120';
|
56 |
+
$show_whats_new = false && apply_filters('admin_menu_editor_is_pro', false) && !empty($editor_data['show_hints'][$hint_id]);
|
57 |
+
if ( $show_whats_new ):
|
58 |
+
?>
|
59 |
+
<div class="ws_hint" id="<?php echo esc_attr($hint_id); ?>">
|
60 |
+
<div class="ws_hint_close" title="Close">x</div>
|
61 |
+
<div class="ws_hint_content">
|
62 |
+
<strong>What's New In 1.20 and 1.30</strong>
|
63 |
+
<ul>
|
64 |
+
<li>New menu permissions interface.
|
65 |
+
<a href="http://w-shadow.com/admin-menu-editor-pro/permissions/">Learn more.</a></li>
|
66 |
+
|
67 |
+
<li>You can now use "not:user:username", "capability1,capability2", "capability1+capability2" and other
|
68 |
+
advanced syntax in the capability field. See the link above for details.</li>
|
69 |
+
|
70 |
+
<li>You can drag sub-menu items to the top level and the other way around. To do it,
|
71 |
+
drag the item to the very end of the (sub-)menu and drop it on the yellow rectangle that will appear.</li>
|
72 |
+
|
73 |
+
<li>Added a "Target page" drop-down to simplify setting menu URLs. You can still enter an arbitrary URL
|
74 |
+
by selecting "Custom".</li>
|
75 |
+
|
76 |
+
<li>Miscellaneous bug fixes.</li>
|
77 |
+
|
78 |
+
</ul>
|
79 |
+
</div>
|
80 |
+
</div>
|
81 |
+
<?php
|
82 |
+
endif;
|
83 |
+
?>
|
84 |
+
|
85 |
+
<?php include dirname(__FILE__) . '/access-editor-dialog.php'; ?>
|
86 |
+
|
87 |
+
<div id='ws_menu_editor'>
|
88 |
+
<div id="ws_actor_selector_container">
|
89 |
+
<ul id="ws_actor_selector" class="subsubsub" style="display: none;">
|
90 |
+
<!-- Contents will be generated by JS -->
|
91 |
+
</ul>
|
92 |
+
<div class="clear"></div>
|
93 |
+
</div>
|
94 |
+
|
95 |
+
<div>
|
96 |
+
|
97 |
+
<div class='ws_main_container'>
|
98 |
+
<div class='ws_toolbar'>
|
99 |
+
<div class="ws_button_container">
|
100 |
+
<a id='ws_cut_menu' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $icons['cut']; ?>' alt="Cut" /></a>
|
101 |
+
<a id='ws_copy_menu' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $icons['copy']; ?>' alt="Copy" /></a>
|
102 |
+
<a id='ws_paste_menu' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $icons['paste']; ?>' alt="Paste" /></a>
|
103 |
+
|
104 |
+
<div class="ws_separator"> </div>
|
105 |
+
|
106 |
+
<a id='ws_new_menu' class='ws_button' href='javascript:void(0)' title='New menu'><img src='<?php echo $icons['new']; ?>' alt="New menu" /></a>
|
107 |
+
<a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
|
108 |
+
<a id='ws_delete_menu' class='ws_button' href='javascript:void(0)' title='Delete menu'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu" /></a>
|
109 |
+
|
110 |
+
<div class="ws_separator"> </div>
|
111 |
+
|
112 |
+
<a id='ws_new_separator' class='ws_button' href='javascript:void(0)' title='New separator'><img src='<?php echo $icons['new-separator']; ?>' alt="New separator" /></a>
|
113 |
+
|
114 |
+
<?php if ( apply_filters('admin_menu_editor_is_pro', false) ): ?>
|
115 |
+
<div class="ws_separator"> </div>
|
116 |
+
|
117 |
+
<a id='ws_toggle_all_menus' class='ws_button' href='javascript:void(0)'
|
118 |
+
title='Toggle all menus for the selected role'><img src='<?php echo $icons['toggle-all']; ?>' alt="Toggle all" /></a>
|
119 |
+
<?php endif; ?>
|
120 |
+
</div>
|
121 |
+
</div>
|
122 |
+
|
123 |
+
<div id='ws_menu_box' class="ws_box">
|
124 |
+
</div>
|
125 |
+
|
126 |
+
<?php do_action('admin_menu_editor_container', 'menu'); ?>
|
127 |
+
</div>
|
128 |
+
|
129 |
+
<div class='ws_main_container'>
|
130 |
+
<div class='ws_toolbar'>
|
131 |
+
<div class="ws_button_container">
|
132 |
+
<a id='ws_cut_item' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $icons['cut']; ?>' alt="Cut" /></a>
|
133 |
+
<a id='ws_copy_item' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $icons['copy']; ?>' alt="Copy" /></a>
|
134 |
+
<a id='ws_paste_item' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $icons['paste']; ?>' alt="Paste" /></a>
|
135 |
+
|
136 |
+
<div class="ws_separator"> </div>
|
137 |
+
|
138 |
+
<a id='ws_new_item' class='ws_button' href='javascript:void(0)' title='New menu item'><img src='<?php echo $icons['new']; ?>' alt="New menu item" /></a>
|
139 |
+
<a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
|
140 |
+
<a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu item" /></a>
|
141 |
+
|
142 |
+
<div class="ws_separator"> </div>
|
143 |
+
|
144 |
+
<?php if ( apply_filters('admin_menu_editor_is_pro', false) ): ?>
|
145 |
+
<a id='ws_new_submenu_separator' class='ws_button' href='javascript:void(0)' title='New separator'><img src='<?php echo $icons['new-separator']; ?>' alt="New separator" /></a>
|
146 |
+
<div class="ws_separator"> </div>
|
147 |
+
<?php endif; ?>
|
148 |
+
|
149 |
+
<a id='ws_sort_ascending' class='ws_button' href='javascript:void(0)' title='Sort ascending'>
|
150 |
+
<img src='<?php echo $images_url; ?>/sort_ascending.png' alt="Sort ascending" />
|
151 |
+
</a>
|
152 |
+
<a id='ws_sort_descending' class='ws_button' href='javascript:void(0)' title='Sort descending'>
|
153 |
+
<img src='<?php echo $images_url; ?>/sort_descending.png' alt="Sort descending" />
|
154 |
+
</a>
|
155 |
+
</div>
|
156 |
+
</div>
|
157 |
+
|
158 |
+
<div id='ws_submenu_box' class="ws_box">
|
159 |
+
</div>
|
160 |
+
|
161 |
+
<?php do_action('admin_menu_editor_container', 'submenu'); ?>
|
162 |
+
</div>
|
163 |
+
|
164 |
+
<div class="ws_basic_container">
|
165 |
+
|
166 |
+
<div class="ws_main_container" id="ws_editor_sidebar">
|
167 |
+
<form method="post" action="<?php echo admin_url('options-general.php?page=menu_editor&noheader=1'); ?>" id='ws_main_form' name='ws_main_form'>
|
168 |
+
<?php wp_nonce_field('menu-editor-form'); ?>
|
169 |
+
<input type="hidden" name="action" value="save_menu">
|
170 |
+
<input type="hidden" name="data" id="ws_data" value="">
|
171 |
+
<input type="hidden" name="data_length" id="ws_data_length" value="">
|
172 |
+
<input type="hidden" name="selected_actor" id="ws_selected_actor" value="">
|
173 |
+
<input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
|
174 |
+
</form>
|
175 |
+
|
176 |
+
<input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
|
177 |
+
<input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
|
178 |
+
|
179 |
+
<?php
|
180 |
+
do_action('admin_menu_editor_sidebar');
|
181 |
+
?>
|
182 |
+
</div>
|
183 |
+
|
184 |
+
<?php
|
185 |
+
$hint_id = 'ws_sidebar_pro_ad';
|
186 |
+
$show_pro_benefits = !apply_filters('admin_menu_editor_is_pro', false) && (!isset($editor_data['show_hints'][$hint_id]) || $editor_data['show_hints'][$hint_id]);
|
187 |
+
|
188 |
+
if ( $show_pro_benefits ):
|
189 |
+
$benefit_variations = array(
|
190 |
+
'Simplified, role-based permissions.',
|
191 |
+
'Role-based menu permissions.',
|
192 |
+
'Simpler, role-based permissions.',
|
193 |
+
);
|
194 |
+
//Pseudo-randomly select one phrase based on the site URL.
|
195 |
+
$variation_index = hexdec( substr(md5(get_site_url()), -1) ) % count($benefit_variations);
|
196 |
+
$selected_variation = $benefit_variations[$variation_index];
|
197 |
+
|
198 |
+
$pro_version_link = 'http://adminmenueditor.com/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=sidebar_link_cv' . $variation_index . '&utm_campaign=Plugins';
|
199 |
+
?>
|
200 |
+
<div class="clear"></div>
|
201 |
+
|
202 |
+
<div class="ws_hint" id="<?php echo esc_attr($hint_id); ?>">
|
203 |
+
<div class="ws_hint_close" title="Close">x</div>
|
204 |
+
<div class="ws_hint_content">
|
205 |
+
<strong>Upgrade to Pro:</strong>
|
206 |
+
<ul>
|
207 |
+
<li><?php echo $selected_variation; ?></li>
|
208 |
+
<li>Drag items between menu levels.</li>
|
209 |
+
<li>Menu export & import.</li>
|
210 |
+
</ul>
|
211 |
+
<a href="<?php echo esc_attr($pro_version_link); ?>" target="_blank">Learn more</a>
|
212 |
+
|
|
213 |
+
<a href="http://amedemo.com/" target="_blank">Try online demo</a>
|
214 |
+
</div>
|
215 |
+
</div>
|
216 |
+
<?php
|
217 |
+
endif;
|
218 |
+
?>
|
219 |
+
|
220 |
+
</div> <!-- / .ws_basic_container -->
|
221 |
+
|
222 |
+
</div>
|
223 |
+
|
224 |
+
<div class="clear"></div>
|
225 |
+
|
226 |
+
</div> <!-- / .ws_menu_editor -->
|
227 |
+
|
228 |
+
</div> <!-- / .wrap -->
|
229 |
+
|
230 |
+
|
231 |
+
|
232 |
+
<?php
|
233 |
+
//Create a pop-up capability selector
|
234 |
+
$capSelector = array('<select id="ws_cap_selector" class="ws_dropdown" size="10">');
|
235 |
+
|
236 |
+
$capSelector[] = '<optgroup label="Roles">';
|
237 |
+
foreach($editor_data['all_roles'] as $role_id => $role_name){
|
238 |
+
$capSelector[] = sprintf(
|
239 |
+
'<option value="%s">%s</option>',
|
240 |
+
esc_attr($role_id),
|
241 |
+
$role_name
|
242 |
+
);
|
243 |
+
}
|
244 |
+
$capSelector[] = '</optgroup>';
|
245 |
+
|
246 |
+
$capSelector[] = '<optgroup label="Capabilities">';
|
247 |
+
foreach($editor_data['all_capabilities'] as $cap){
|
248 |
+
$capSelector[] = sprintf(
|
249 |
+
'<option value="%s">%s</option>',
|
250 |
+
esc_attr($cap),
|
251 |
+
$cap
|
252 |
+
);
|
253 |
+
}
|
254 |
+
$capSelector[] = '</optgroup>';
|
255 |
+
$capSelector[] = '</select>';
|
256 |
+
|
257 |
+
echo implode("\n", $capSelector);
|
258 |
+
?>
|
259 |
+
|
260 |
+
<!-- Menu icon selector widget -->
|
261 |
+
<div id="ws_icon_selector" style="display: none;">
|
262 |
+
<?php
|
263 |
+
//Let the user select a custom icon via the media uploader.
|
264 |
+
//We only support the new WP 3.5+ media API. Hence the function_exists() check.
|
265 |
+
if ( function_exists('wp_enqueue_media') ):
|
266 |
+
?>
|
267 |
+
<input type="button" class="button"
|
268 |
+
id="ws_choose_icon_from_media"
|
269 |
+
title="Upload an image or choose one from your media library"
|
270 |
+
value="Choose Icon">
|
271 |
+
<div class="clear"></div>
|
272 |
+
<?php
|
273 |
+
endif;
|
274 |
+
?>
|
275 |
+
|
276 |
+
<?php
|
277 |
+
$defaultWpIcons = array(
|
278 |
+
'generic', 'dashboard', 'post', 'media', 'links', 'page', 'comments',
|
279 |
+
'appearance', 'plugins', 'users', 'tools', 'settings', 'site',
|
280 |
+
);
|
281 |
+
foreach($defaultWpIcons as $icon) {
|
282 |
+
printf(
|
283 |
+
'<div class="ws_icon_option" title="%1$s" data-icon-class="menu-icon-%2$s">
|
284 |
+
<div class="ws_icon_image icon16 icon-%2$s"><br></div>
|
285 |
+
</div>',
|
286 |
+
esc_attr(ucwords($icon)),
|
287 |
+
$icon
|
288 |
+
);
|
289 |
+
}
|
290 |
+
|
291 |
+
$defaultIconImages = array(
|
292 |
+
'images/generic.png',
|
293 |
+
);
|
294 |
+
foreach($defaultIconImages as $icon) {
|
295 |
+
printf(
|
296 |
+
'<div class="ws_icon_option" data-icon-url="%1$s">
|
297 |
+
<img src="%1$s">
|
298 |
+
</div>',
|
299 |
+
esc_attr($icon)
|
300 |
+
);
|
301 |
+
}
|
302 |
+
?>
|
303 |
+
<div class="ws_icon_option ws_custom_image_icon" title="Custom image" style="display: none;">
|
304 |
+
<img src="<?php echo esc_attr(admin_url('images/loading.gif')); ?>">
|
305 |
+
</div>
|
306 |
+
<div class="clear"></div>
|
307 |
+
</div>
|
308 |
+
|
309 |
+
<span id="ws-ame-screen-meta-contents" style="display:none;">
|
310 |
+
<label for="ws-hide-advanced-settings">
|
311 |
+
<input type="checkbox" id="ws-hide-advanced-settings"<?php
|
312 |
+
if ( $this->options['hide_advanced_settings'] ){
|
313 |
+
echo ' checked="checked"';
|
314 |
+
}
|
315 |
+
?> /> Hide advanced options
|
316 |
+
</label>
|
317 |
+
</span>
|
318 |
+
|
319 |
+
<script type='text/javascript'>
|
320 |
+
var defaultMenu = <?php echo $editor_data['default_menu_js']; ?>;
|
321 |
+
var customMenu = <?php echo $editor_data['custom_menu_js']; ?>;
|
322 |
+
</script>
|
323 |
+
|
324 |
+
<?php
|
325 |
+
|
326 |
+
//Let the Pro version script output it's extra HTML & scripts.
|
327 |
+
do_action('admin_menu_editor_footer');
|
includes/menu-editor-core.php
CHANGED
@@ -8,47 +8,94 @@ if (class_exists('WPMenuEditor')){
|
|
8 |
);
|
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 |
-
|
17 |
|
18 |
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
|
|
|
|
|
19 |
|
20 |
-
|
21 |
-
protected $
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
|
26 |
//fix the titles of moved plugin pages.
|
27 |
-
private $
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
//Our personal copy of the request vars, without any "magic quotes".
|
33 |
private $post = array();
|
34 |
private $get = array();
|
35 |
|
36 |
function init(){
|
37 |
-
|
38 |
-
|
39 |
-
if ( $this->is_super_plugin() ){
|
40 |
-
$this->sitewide_options = true;
|
41 |
-
}
|
42 |
-
|
43 |
//Set some plugin-specific options
|
44 |
if ( empty($this->option_name) ){
|
45 |
$this->option_name = 'ws_menu_editor';
|
46 |
}
|
47 |
$this->defaults = array(
|
48 |
'hide_advanced_settings' => true,
|
49 |
-
'
|
50 |
-
'display_survey_notice' => true,
|
51 |
'first_install_time' => null,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
);
|
53 |
$this->serialize_with_json = false; //(Don't) store the options in JSON format
|
54 |
|
@@ -57,51 +104,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
57 |
$this->magic_hooks = true;
|
58 |
$this->magic_hook_priority = 99999;
|
59 |
|
60 |
-
//Build some template arrays
|
61 |
-
$this->templates['basic_defaults'] = array(
|
62 |
-
'page_title' => '',
|
63 |
-
'menu_title' => '',
|
64 |
-
'access_level' => 'read',
|
65 |
-
'file' => '',
|
66 |
-
'css_class' => '',
|
67 |
-
'hookname' => '',
|
68 |
-
'icon_url' => '',
|
69 |
-
'position' => 0,
|
70 |
-
'separator' => false,
|
71 |
-
'custom' => false,
|
72 |
-
'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
|
73 |
-
);
|
74 |
-
|
75 |
-
//Template for a basic top-level menu
|
76 |
-
$this->templates['blank_menu'] = array(
|
77 |
-
'page_title' => null,
|
78 |
-
'menu_title' => null,
|
79 |
-
'access_level' => null,
|
80 |
-
'file' => null,
|
81 |
-
'css_class' => null,
|
82 |
-
'hookname' => null,
|
83 |
-
'icon_url' => null,
|
84 |
-
'position' => null,
|
85 |
-
'separator' => null,
|
86 |
-
'custom' => null,
|
87 |
-
'open_in' => null,
|
88 |
-
'defaults' => $this->templates['basic_defaults'],
|
89 |
-
'items' => array(),
|
90 |
-
);
|
91 |
-
//Template for menu items
|
92 |
-
$this->templates['blank_item'] = array(
|
93 |
-
'menu_title' => null,
|
94 |
-
'access_level' => null,
|
95 |
-
'file' => null,
|
96 |
-
'page_title' => null,
|
97 |
-
'position' => null,
|
98 |
-
'custom' => null,
|
99 |
-
'open_in' => null,
|
100 |
-
'defaults' => $this->templates['basic_defaults'],
|
101 |
-
);
|
102 |
-
|
103 |
//AJAXify screen options
|
104 |
-
add_action(
|
105 |
|
106 |
//AJAXify hints
|
107 |
add_action('wp_ajax_ws_ame_hide_hint', array($this, 'ajax_hide_hint'));
|
@@ -109,38 +113,52 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
109 |
//Make sure we have access to the original, un-mangled request data.
|
110 |
//This is necessary because WordPress will stupidly apply "magic quotes"
|
111 |
//to the request vars even if this PHP misfeature is disabled.
|
112 |
-
|
113 |
|
114 |
add_action('admin_enqueue_scripts', array($this, 'enqueue_menu_fix_script'));
|
115 |
|
|
|
|
|
|
|
|
|
116 |
//User survey
|
117 |
add_action('admin_notices', array($this, 'display_survey_notice'));
|
118 |
}
|
119 |
-
|
120 |
function init_finish() {
|
121 |
parent::init_finish();
|
|
|
122 |
|
123 |
-
if ( !isset($this->options['first_install_time']) ) {
|
124 |
-
$this->options['first_install_time'] = time();
|
125 |
-
$this->save_options();
|
126 |
-
}
|
127 |
-
}
|
128 |
-
|
129 |
-
/**
|
130 |
-
* Activation hook
|
131 |
-
*
|
132 |
-
* @return void
|
133 |
-
*/
|
134 |
-
function activate(){
|
135 |
//If we have no stored settings for this version of the plugin, try importing them
|
136 |
//from other versions (i.e. the free or the Pro version).
|
137 |
if ( !$this->load_options() ){
|
138 |
$this->import_settings();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
}
|
140 |
|
141 |
-
|
|
|
|
|
|
|
142 |
}
|
143 |
-
|
144 |
/**
|
145 |
* Import settings from a different version of the plugin.
|
146 |
*
|
@@ -153,71 +171,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
153 |
return true;
|
154 |
}
|
155 |
}
|
156 |
-
|
157 |
return false;
|
158 |
}
|
159 |
|
160 |
-
/**
|
161 |
-
* Add the JS required by the editor to the page header
|
162 |
-
*
|
163 |
-
* @return void
|
164 |
-
*/
|
165 |
-
function enqueue_scripts(){
|
166 |
-
//jQuery JSON plugin
|
167 |
-
wp_enqueue_script('jquery-json', plugins_url('js/jquery.json-1.3.js', $this->plugin_file), array('jquery'), '1.3');
|
168 |
-
//jQuery sort plugin
|
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.
|
187 |
-
wp_localize_script(
|
188 |
-
'menu-editor',
|
189 |
-
'wsEditorData',
|
190 |
-
array(
|
191 |
-
'adminAjaxUrl' => admin_url('admin-ajax.php'),
|
192 |
-
'showHints' => $this->get_hint_visibility(),
|
193 |
-
)
|
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(), '20130805');
|
219 |
-
}
|
220 |
-
|
221 |
/**
|
222 |
* Create a configuration page and load the custom menu
|
223 |
*
|
@@ -225,706 +181,994 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
225 |
*/
|
226 |
function hook_admin_menu(){
|
227 |
global $menu, $submenu;
|
228 |
-
|
229 |
//Menu reset (for emergencies). Executed by accessing http://example.com/wp-admin/?reset_admin_menu=1
|
230 |
$reset_requested = isset($this->get['reset_admin_menu']) && $this->get['reset_admin_menu'];
|
231 |
if ( $reset_requested && $this->current_user_can_edit_menu() ){
|
232 |
-
$this->
|
233 |
-
$this->save_options();
|
234 |
}
|
235 |
|
236 |
//The menu editor is only visible to users with the manage_options privilege.
|
237 |
//Or, if the plugin is installed in mu-plugins, only to the site administrator(s).
|
238 |
if ( $this->current_user_can_edit_menu() ){
|
|
|
|
|
239 |
$page = add_options_page(
|
240 |
apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'),
|
241 |
apply_filters('admin_menu_editor-self_menu_title', 'Menu Editor'),
|
242 |
-
'manage_options',
|
243 |
'menu_editor',
|
244 |
array(&$this, 'page_menu_editor')
|
245 |
);
|
246 |
//Output our JS & CSS on that page only
|
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", "
|
255 |
-
}
|
256 |
-
|
257 |
-
//WP 3.0 in multisite mode has two separators with the same filename. This plugin
|
258 |
-
//expects all top-level menus to have unique filenames/URLs.
|
259 |
-
$first_separator1 = -1;
|
260 |
-
$last_separator1 = -1;
|
261 |
-
foreach($menu as $index => $item){
|
262 |
-
if ( $item[2] == 'separator1' ){
|
263 |
-
$last_separator1 = $index;
|
264 |
-
if ( $first_separator1 == -1 ){
|
265 |
-
$first_separator1 = $index;
|
266 |
-
}
|
267 |
-
}
|
268 |
-
}
|
269 |
-
if ( $first_separator1 != $last_separator1 ){
|
270 |
-
$menu[$first_separator1][2] = 'separator0';
|
271 |
}
|
272 |
|
273 |
//Store the "original" menus for later use in the editor
|
274 |
$this->default_wp_menu = $menu;
|
275 |
$this->default_wp_submenu = $submenu;
|
276 |
|
|
|
|
|
|
|
277 |
//Is there a custom menu to use?
|
278 |
-
|
279 |
-
|
280 |
-
if ( empty($this->options['menu_format_version']) || ($this->options['menu_format_version'] < $this->menu_format_version) ){
|
281 |
-
$this->options['custom_menu'] = $this->upgrade_menu_structure($this->options['custom_menu']);
|
282 |
-
$this->options['menu_format_version'] = $this->menu_format_version;
|
283 |
-
$this->save_options();
|
284 |
-
}
|
285 |
//Merge in data from the default menu
|
286 |
-
$tree = $this->menu_merge($
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
//
|
292 |
-
|
293 |
-
$this->
|
294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
}
|
296 |
}
|
297 |
|
298 |
/**
|
299 |
-
*
|
300 |
*
|
301 |
-
* @
|
|
|
302 |
*/
|
303 |
-
function
|
304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
}
|
306 |
|
307 |
/**
|
308 |
-
*
|
309 |
*
|
310 |
-
* @
|
311 |
-
* @return array
|
312 |
-
*/
|
313 |
-
function hook_menu_order($menu_order){
|
314 |
-
if (empty($this->custom_menu)){
|
315 |
-
return $menu_order;
|
316 |
-
}
|
317 |
-
$custom_menu_order = array();
|
318 |
-
foreach($this->filtered_wp_menu as $topmenu){
|
319 |
-
$filename = $topmenu[2];
|
320 |
-
if ( in_array($filename, $menu_order) ){
|
321 |
-
$custom_menu_order[] = $filename;
|
322 |
-
}
|
323 |
-
}
|
324 |
-
return $custom_menu_order;
|
325 |
-
}
|
326 |
-
|
327 |
-
/**
|
328 |
-
* Determine if the current user may use the menu editor.
|
329 |
-
*
|
330 |
-
* @return bool
|
331 |
-
*/
|
332 |
-
function current_user_can_edit_menu(){
|
333 |
-
if ( $this->is_super_plugin() ){
|
334 |
-
return is_super_admin();
|
335 |
-
} else {
|
336 |
-
return current_user_can('manage_options');
|
337 |
-
}
|
338 |
-
}
|
339 |
-
|
340 |
-
/**
|
341 |
-
* Intercept a handy action to fix the page title for moved plugin pages.
|
342 |
-
*
|
343 |
-
* @return void
|
344 |
-
*/
|
345 |
-
function hook_admin_xml_ns(){
|
346 |
-
global $title;
|
347 |
-
if ( empty($title) ){
|
348 |
-
$title = $this->get_real_page_title();
|
349 |
-
}
|
350 |
-
}
|
351 |
-
|
352 |
-
/**
|
353 |
-
* Fix the page title for move plugin pages.
|
354 |
-
* The 'admin_title' filter is only available in WP 3.1+
|
355 |
-
*
|
356 |
-
* @param string $admin_title The current admin title.
|
357 |
-
* @param string $title The current page title.
|
358 |
-
* @return string New admin title.
|
359 |
*/
|
360 |
-
function
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
return $admin_title;
|
365 |
}
|
366 |
-
|
367 |
/**
|
368 |
-
*
|
369 |
-
*
|
370 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
371 |
*/
|
372 |
-
function
|
373 |
-
global $
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
$
|
379 |
-
|
380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
381 |
}
|
382 |
}
|
383 |
-
|
384 |
-
return $title;
|
385 |
-
}
|
386 |
-
|
387 |
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
|
|
403 |
}
|
|
|
404 |
|
405 |
-
|
406 |
-
|
|
|
|
|
|
|
|
|
407 |
}
|
408 |
}
|
409 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
410 |
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
return $this->json_encode($tree);
|
419 |
-
}
|
420 |
|
421 |
-
|
422 |
-
|
423 |
-
*
|
424 |
-
* @param array $item An element of the $menu array
|
425 |
-
* @param integer $pos The position (index) of the menu item
|
426 |
-
* @return array
|
427 |
-
*/
|
428 |
-
function menu2assoc($item, $pos=0){
|
429 |
-
$item = array(
|
430 |
-
'menu_title' => $item[0],
|
431 |
-
'access_level' => $item[1],
|
432 |
-
'file' => $item[2],
|
433 |
-
'page_title' => $item[3],
|
434 |
-
'css_class' => $item[4],
|
435 |
-
'hookname' => (isset($item[5])?$item[5]:''), //ID
|
436 |
-
'icon_url' => (isset($item[6])?$item[6]:''),
|
437 |
-
'position' => $pos,
|
438 |
-
);
|
439 |
-
$item['separator'] = strpos($item['css_class'], 'wp-menu-separator') !== false;
|
440 |
-
//Flag plugin pages
|
441 |
-
$item['is_plugin_page'] = (get_plugin_page_hook($item['file'], '') != null);
|
442 |
-
|
443 |
-
return array_merge($this->templates['basic_defaults'], $item);
|
444 |
-
}
|
445 |
|
446 |
-
|
447 |
-
* Convert a WP submenu structure to an associative array
|
448 |
-
*
|
449 |
-
* @param array $item An element of the $submenu array
|
450 |
-
* @param integer $pos The position (index) of that element
|
451 |
-
* @param string $parent Parent file that this menu item belongs to.
|
452 |
-
* @return array
|
453 |
-
*/
|
454 |
-
function submenu2assoc($item, $pos = 0, $parent = ''){
|
455 |
-
$item = array(
|
456 |
-
'menu_title' => $item[0],
|
457 |
-
'access_level' => $item[1],
|
458 |
-
'file' => $item[2],
|
459 |
-
'page_title' => (isset($item[3])?$item[3]:''),
|
460 |
-
'position' => $pos,
|
461 |
-
);
|
462 |
-
//Save the default parent menu
|
463 |
-
$item['parent'] = $parent;
|
464 |
-
//Flag plugin pages
|
465 |
-
$item['is_plugin_page'] = (get_plugin_page_hook($item['file'], $parent) != null);
|
466 |
-
|
467 |
-
return array_merge($this->templates['basic_defaults'], $item);
|
468 |
}
|
469 |
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
//
|
480 |
-
$
|
481 |
-
|
482 |
-
|
483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
484 |
}
|
485 |
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
//File itself is not guaranteed to be unique, so we use a surrogate ID to identify submenus.
|
492 |
-
$uid = $this->unique_submenu_id($item['file'], $parent);
|
493 |
-
$submenu_defaults[$uid] = $item;
|
494 |
-
}
|
495 |
}
|
496 |
|
497 |
-
|
498 |
-
|
499 |
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
*
|
504 |
-
* @param array $tree A menu in plugin's internal form
|
505 |
-
* @param array $menu WordPress menu structure
|
506 |
-
* @param array $submenu WordPress submenu structure
|
507 |
-
* @return array Updated menu tree
|
508 |
-
*/
|
509 |
-
function menu_merge($tree, $menu, $submenu){
|
510 |
-
list($menu_defaults, $submenu_defaults) = $this->build_lookups($menu, $submenu);
|
511 |
|
512 |
-
|
513 |
-
foreach
|
514 |
-
$
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
$topmenu['defaults'] = $menu_defaults[$topfile];
|
519 |
-
//Note that the original item was used
|
520 |
-
$menu_defaults[$topfile]['used'] = true;
|
521 |
-
} else {
|
522 |
-
//Record the menu as missing, unless it's a menu separator
|
523 |
-
if ( empty($topmenu['separator']) ){
|
524 |
-
$topmenu['missing'] = true;
|
525 |
-
//[Nasty] Fill the 'defaults' array for menu's that don't have it.
|
526 |
-
//This should never be required - saving a custom menu should set the defaults
|
527 |
-
//for all menus it contains automatically.
|
528 |
-
if ( empty($topmenu['defaults']) ){
|
529 |
-
$tmp = $topmenu;
|
530 |
-
$topmenu['defaults'] = $tmp;
|
531 |
-
}
|
532 |
-
}
|
533 |
-
}
|
534 |
|
535 |
-
|
536 |
-
|
537 |
-
foreach ($topmenu['items'] as $file => &$item){
|
538 |
-
$uid = $this->unique_submenu_id($item, $topfile);
|
539 |
-
|
540 |
-
//Is this item present in the default WP menu?
|
541 |
-
if (isset($submenu_defaults[$uid])){
|
542 |
-
//Yes, load defaults from that item
|
543 |
-
$item['defaults'] = $submenu_defaults[$uid];
|
544 |
-
$submenu_defaults[$uid]['used'] = true;
|
545 |
-
} else {
|
546 |
-
//Record as missing
|
547 |
-
$item['missing'] = true;
|
548 |
-
if ( empty($item['defaults']) ){
|
549 |
-
$tmp = $item;
|
550 |
-
$item['defaults'] = $tmp;
|
551 |
-
}
|
552 |
-
}
|
553 |
-
}
|
554 |
-
}
|
555 |
}
|
556 |
|
557 |
-
//
|
558 |
-
|
559 |
-
unset($item);
|
560 |
|
561 |
-
|
562 |
-
|
563 |
-
|
|
|
|
|
|
|
|
|
|
|
564 |
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
582 |
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
599 |
}
|
|
|
600 |
|
601 |
-
|
602 |
-
|
|
|
603 |
|
604 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
605 |
}
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
|
|
|
|
618 |
}
|
619 |
-
$
|
620 |
-
|
621 |
-
|
622 |
-
if ( !empty($parent) ){
|
623 |
-
return $parent . '::' . $file;
|
624 |
} else {
|
625 |
-
|
|
|
626 |
}
|
|
|
|
|
|
|
627 |
}
|
628 |
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
633 |
-
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
640 |
-
|
641 |
-
$
|
642 |
-
|
643 |
-
$tree_item['separator'] = empty($item[2]) || empty($item[0]) || (strpos($item[4], 'wp-menu-separator') !== false);
|
644 |
-
|
645 |
-
if ( empty($tree_item['defaults']['file']) ){
|
646 |
-
$tree_item['defaults']['file'] = 'separator_'.$separator_count;
|
647 |
-
$separator_count++;
|
648 |
}
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
if ( isset($submenu[$parent]) ){
|
653 |
-
foreach($submenu[$parent] as $subitem_pos => $subitem){
|
654 |
-
$tree_item['items'][$subitem[2]] = array_merge(
|
655 |
-
$this->templates['blank_item'],
|
656 |
-
array('defaults' => $this->submenu2assoc($subitem, $subitem_pos, $parent))
|
657 |
-
);
|
658 |
-
}
|
659 |
}
|
660 |
-
|
661 |
-
$tree[$parent] = $tree_item;
|
662 |
}
|
663 |
|
664 |
-
|
|
|
665 |
|
666 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
667 |
}
|
668 |
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
}
|
685 |
-
return $item;
|
686 |
}
|
687 |
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
*/
|
703 |
-
function apply_menu_filters($item, $item_type = '', $extra = null){
|
704 |
-
if ( empty($item_type) ){
|
705 |
-
//Only top-level menus have an icon
|
706 |
-
$item_type = isset($item['icon_url'])?'menu':'submenu';
|
707 |
}
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
712 |
}
|
713 |
-
|
714 |
-
return $
|
715 |
}
|
716 |
|
717 |
/**
|
718 |
-
*
|
719 |
-
*
|
720 |
-
* specified field is not set in the item itself.
|
721 |
*
|
722 |
-
* @param array $
|
723 |
-
* @param
|
724 |
-
* @
|
725 |
-
* @return mixed Field value.
|
726 |
*/
|
727 |
-
function
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
735 |
}
|
736 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
737 |
}
|
738 |
|
739 |
/**
|
740 |
-
*
|
|
|
741 |
*
|
742 |
-
* @
|
743 |
-
*
|
744 |
-
* @
|
|
|
745 |
*/
|
746 |
-
function
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
if (
|
751 |
-
$
|
752 |
-
|
753 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
754 |
}
|
755 |
}
|
756 |
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
762 |
} else {
|
763 |
-
$
|
764 |
}
|
765 |
}
|
766 |
|
767 |
-
return $p1 - $p2;
|
768 |
-
}
|
769 |
-
|
770 |
-
/**
|
771 |
-
* Sort the menus and menu items of a given menu according to their positions
|
772 |
-
*
|
773 |
-
* @param array $tree A menu structure in the internal format
|
774 |
-
* @return array Sorted menu in the internal format
|
775 |
-
*/
|
776 |
-
function sort_menu_tree($tree){
|
777 |
//Resort the tree to ensure the found items are in the right spots
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
783 |
}
|
|
|
|
|
784 |
}
|
785 |
-
|
786 |
-
|
|
|
|
|
|
|
|
|
787 |
}
|
788 |
|
789 |
/**
|
790 |
-
*
|
791 |
*
|
792 |
-
*
|
793 |
-
*
|
794 |
-
*
|
795 |
-
*
|
|
|
|
|
796 |
*
|
797 |
-
* @
|
798 |
-
* @
|
|
|
|
|
|
|
799 |
*/
|
800 |
-
function
|
801 |
-
$
|
802 |
-
$
|
803 |
-
$
|
|
|
804 |
|
805 |
//Sort the menu by position
|
806 |
-
uasort($tree,
|
807 |
|
808 |
//Prepare the top menu
|
809 |
$first_nonseparator_found = false;
|
810 |
foreach ($tree as $topmenu){
|
811 |
-
|
812 |
-
//Skip missing
|
813 |
-
$
|
814 |
-
if ( !empty($topmenu['missing']) && !$custom ) {
|
815 |
continue;
|
816 |
-
}
|
817 |
|
818 |
//Skip leading menu separators. Fixes a superfluous separator showing up
|
819 |
//in WP 3.0 (multisite mode) when there's a custom menu and the current user
|
820 |
//can't access its first item ("Super Admin").
|
821 |
-
if ( !empty($topmenu['separator']) && !$first_nonseparator_found )
|
822 |
-
|
|
|
823 |
$first_nonseparator_found = true;
|
824 |
|
825 |
-
|
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 |
-
|
843 |
-
|
844 |
-
|
845 |
-
//Build the menu structure that WP expects
|
846 |
-
$menu[] = array(
|
847 |
-
$topmenu['menu_title'],
|
848 |
-
$topmenu['access_level'],
|
849 |
-
$topmenu['file'],
|
850 |
-
$topmenu['page_title'],
|
851 |
-
$topmenu['css_class'],
|
852 |
-
$topmenu['hookname'], //ID
|
853 |
-
$topmenu['icon_url']
|
854 |
-
);
|
855 |
|
856 |
//Prepare the submenu of this menu
|
|
|
857 |
if( !empty($topmenu['items']) ){
|
858 |
$items = $topmenu['items'];
|
859 |
//Sort by position
|
860 |
-
uasort($items,
|
861 |
|
862 |
foreach ($items as $item) {
|
863 |
-
|
864 |
-
|
865 |
-
$custom = $this->get_menu_field($item, 'custom', false);
|
866 |
-
if ( !empty($item['missing']) && !$custom ) continue;
|
867 |
-
|
868 |
-
//Special case : plugin pages that have been moved to a different menu.
|
869 |
-
//If the file field hasn't already been modified, we'll need to adjust it
|
870 |
-
//to point to the old parent. This is required because WP identifies
|
871 |
-
//plugin pages using *both* the plugin file and the parent file.
|
872 |
-
if ( $this->get_menu_field($item, 'is_plugin_page', false) && ($item['file'] === null) ){
|
873 |
-
$default_parent = '';
|
874 |
-
if ( isset($item['defaults']) && isset($item['defaults']['parent'])){
|
875 |
-
$default_parent = $item['defaults']['parent'];
|
876 |
-
}
|
877 |
-
if ( $topmenu['file'] != $default_parent ){
|
878 |
-
$item['file'] = $default_parent . '?page=' . $item['defaults']['file'];
|
879 |
-
}
|
880 |
-
}
|
881 |
-
|
882 |
-
$item = $this->apply_defaults($item);
|
883 |
-
$item = $this->apply_menu_filters($item, 'submenu', $topmenu['file']);
|
884 |
-
|
885 |
-
//Skip hidden items
|
886 |
-
if (!empty($item['hidden'])) {
|
887 |
continue;
|
888 |
}
|
889 |
-
|
890 |
-
$submenu
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
);
|
896 |
-
|
897 |
-
//Make a note of the page's correct title so we can fix it later
|
898 |
-
//if necessary.
|
899 |
-
$title_lookup[$item['file']] = $item['menu_title'];
|
900 |
}
|
901 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
902 |
}
|
903 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
904 |
}
|
905 |
-
|
906 |
-
|
907 |
-
|
908 |
-
|
909 |
-
|
910 |
-
|
911 |
-
|
912 |
-
|
913 |
-
|
914 |
-
|
915 |
-
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
924 |
}
|
925 |
}
|
926 |
-
|
927 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
928 |
}
|
929 |
|
930 |
/**
|
@@ -933,424 +1177,264 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
933 |
* @return void
|
934 |
*/
|
935 |
function page_menu_editor(){
|
936 |
-
global $menu, $submenu;
|
937 |
-
global $wp_roles;
|
938 |
-
|
939 |
if ( !$this->current_user_can_edit_menu() ){
|
940 |
-
|
|
|
|
|
|
|
941 |
}
|
942 |
|
943 |
$action = isset($this->post['action']) ? $this->post['action'] : (isset($this->get['action']) ? $this->get['action'] : '');
|
944 |
do_action('admin_menu_editor_header', $action);
|
945 |
-
|
946 |
-
//Handle form submissions
|
947 |
-
if (isset($this->post['data'])){
|
948 |
-
check_admin_referer('menu-editor-form');
|
949 |
-
|
950 |
-
//Try to decode a menu tree encoded as JSON
|
951 |
-
$data = $this->json_decode($this->post['data'], true);
|
952 |
-
if (!$data || (count($data) < 2) ){
|
953 |
-
$fixed = stripslashes($this->post['data']);
|
954 |
-
$data = $this->json_decode( $fixed, true );
|
955 |
-
}
|
956 |
|
957 |
-
|
958 |
-
|
959 |
-
|
960 |
-
|
961 |
-
|
962 |
-
|
963 |
-
|
964 |
-
|
965 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
966 |
}
|
967 |
|
968 |
//Save the custom menu
|
969 |
-
$this->
|
970 |
-
|
971 |
-
//Redirect back to the editor and display the success message
|
972 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
973 |
} else {
|
974 |
-
|
975 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
976 |
}
|
977 |
-
die();
|
978 |
-
}
|
979 |
|
980 |
-
|
981 |
-
if ( !apply_filters('admin_menu_editor_is_pro', false) ){
|
982 |
-
$this->print_upgrade_notice();
|
983 |
-
}
|
984 |
-
?>
|
985 |
-
<div class="wrap">
|
986 |
-
<h2>
|
987 |
-
<?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
|
988 |
-
</h2>
|
989 |
|
990 |
-
|
991 |
-
|
992 |
-
|
993 |
-
|
994 |
-
|
995 |
-
|
996 |
-
|
997 |
-
|
998 |
-
|
999 |
-
|
1000 |
-
|
1001 |
-
|
1002 |
-
|
1003 |
-
|
1004 |
-
|
1005 |
-
|
1006 |
-
|
1007 |
-
|
1008 |
-
$custom_menu = $default_menu;
|
1009 |
-
}
|
1010 |
-
|
1011 |
-
//Encode both menus as JSON
|
1012 |
-
$default_menu_js = $this->getMenuAsJS($default_menu);
|
1013 |
-
$custom_menu_js = $this->getMenuAsJS($custom_menu);
|
1014 |
-
|
1015 |
-
$plugin_url = $this->plugin_dir_url;
|
1016 |
-
$images_url = plugins_url('images', $this->plugin_file);
|
1017 |
-
|
1018 |
-
$icons = array(
|
1019 |
-
'cut' => '/gnome-icon-theme/edit-cut-blue.png',
|
1020 |
-
'copy' => '/gion/edit-copy.png',
|
1021 |
-
'paste' => '/gnome-icon-theme/edit-paste.png',
|
1022 |
-
'hide' => '/icon-extension-grey.png',
|
1023 |
-
'new' => '/page-add.png',
|
1024 |
-
'delete' => '/page-delete.png',
|
1025 |
-
'new-separator' => '/separator-add.png',
|
1026 |
-
);
|
1027 |
-
foreach($icons as $name => $url) {
|
1028 |
-
$icons[$name] = $images_url . $url;
|
1029 |
-
}
|
1030 |
-
|
1031 |
-
//Create a list of all known capabilities and roles. Used for the dropdown list on the access field.
|
1032 |
-
$all_capabilities = $this->get_all_capabilities();
|
1033 |
-
//"level_X" capabilities are deprecated so we don't want people using them.
|
1034 |
-
//This would look better with array_filter() and an anonymous function as a callback.
|
1035 |
-
for($level = 0; $level <= 10; $level++){
|
1036 |
-
$cap = 'level_' . $level;
|
1037 |
-
if ( isset($all_capabilities[$cap]) ){
|
1038 |
-
unset($all_capabilities[$cap]);
|
1039 |
-
}
|
1040 |
-
}
|
1041 |
-
$all_capabilities = array_keys($all_capabilities);
|
1042 |
-
natcasesort($all_capabilities);
|
1043 |
-
|
1044 |
-
$all_roles = $this->get_all_roles();
|
1045 |
-
//Multi-site installs also get the virtual "Super Admin" role
|
1046 |
-
if ( is_multisite() ){
|
1047 |
-
$all_roles['super_admin'] = 'Super Admin';
|
1048 |
-
}
|
1049 |
-
asort($all_roles);
|
1050 |
-
?>
|
1051 |
-
<div id='ws_menu_editor'>
|
1052 |
-
<div class='ws_main_container'>
|
1053 |
-
<div class='ws_toolbar'>
|
1054 |
-
<div class="ws_button_container">
|
1055 |
-
<a id='ws_cut_menu' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $icons['cut']; ?>' alt="Cut" /></a>
|
1056 |
-
<a id='ws_copy_menu' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $icons['copy']; ?>' alt="Copy" /></a>
|
1057 |
-
<a id='ws_paste_menu' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $icons['paste']; ?>' alt="Paste" /></a>
|
1058 |
-
|
1059 |
-
<div class="ws_separator"> </div>
|
1060 |
|
1061 |
-
|
1062 |
-
|
1063 |
-
|
1064 |
-
|
1065 |
-
|
|
|
|
|
|
|
1066 |
|
1067 |
-
|
1068 |
-
|
1069 |
-
|
1070 |
-
|
1071 |
-
|
1072 |
-
|
1073 |
-
|
1074 |
-
<div class='ws_main_container'>
|
1075 |
-
<div class='ws_toolbar'>
|
1076 |
-
<div class="ws_button_container">
|
1077 |
-
<a id='ws_cut_item' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $icons['cut']; ?>' alt="Cut" /></a>
|
1078 |
-
<a id='ws_copy_item' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $icons['copy']; ?>' alt="Copy" /></a>
|
1079 |
-
<a id='ws_paste_item' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $icons['paste']; ?>' alt="Paste" /></a>
|
1080 |
-
|
1081 |
-
<div class="ws_separator"> </div>
|
1082 |
|
1083 |
-
|
1084 |
-
|
1085 |
-
<a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu item" /></a>
|
1086 |
-
|
1087 |
-
<div class="ws_separator"> </div>
|
1088 |
-
|
1089 |
-
<a id='ws_sort_ascending' class='ws_button' href='javascript:void(0)' title='Sort ascending'>
|
1090 |
-
<img src='<?php echo $images_url; ?>/sort_ascending.png' alt="Sort ascending" />
|
1091 |
-
</a>
|
1092 |
-
<a id='ws_sort_descending' class='ws_button' href='javascript:void(0)' title='Sort descending'>
|
1093 |
-
<img src='<?php echo $images_url; ?>/sort_descending.png' alt="Sort descending" />
|
1094 |
-
</a>
|
1095 |
-
</div>
|
1096 |
-
</div>
|
1097 |
-
|
1098 |
-
<div id='ws_submenu_box' class="ws_box">
|
1099 |
-
</div>
|
1100 |
-
</div>
|
1101 |
-
</div>
|
1102 |
-
|
1103 |
-
<div class="ws_main_container" id="ws_editor_sidebar">
|
1104 |
-
<form method="post" action="<?php echo admin_url('options-general.php?page=menu_editor&noheader=1'); ?>" id='ws_main_form' name='ws_main_form'>
|
1105 |
-
<?php wp_nonce_field('menu-editor-form'); ?>
|
1106 |
-
<input type="hidden" name="data" id="ws_data" value="">
|
1107 |
-
<input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
|
1108 |
-
</form>
|
1109 |
-
|
1110 |
-
<input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
|
1111 |
-
<input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
|
1112 |
-
|
1113 |
-
<?php
|
1114 |
-
do_action('admin_menu_editor_sidebar');
|
1115 |
-
?>
|
1116 |
-
</div>
|
1117 |
-
|
1118 |
-
<?php
|
1119 |
-
$show_hints = $this->get_hint_visibility();
|
1120 |
-
$hint_id = 'ws_sidebar_pro_ad';
|
1121 |
-
$show_pro_benefits = !apply_filters('admin_menu_editor_is_pro', false) && (!isset($show_hints[$hint_id]) || $show_hints[$hint_id]);
|
1122 |
-
if ( $show_pro_benefits ):
|
1123 |
-
$benefit_variations = array(
|
1124 |
-
'Simplified, role-based permissions.',
|
1125 |
-
'Role-based menu permissions',
|
1126 |
-
'Per-role menu permissions',
|
1127 |
-
);
|
1128 |
-
//Pseudo-randomly select one phrase based on the site URL.
|
1129 |
-
$variation_index = hexdec( substr(md5(get_site_url()), -1) ) % count($benefit_variations);
|
1130 |
-
$selected_variation = $benefit_variations[$variation_index];
|
1131 |
-
|
1132 |
-
$pro_version_link = 'http://adminmenueditor.com/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=sidebar_link_bv' . $variation_index . '&utm_campaign=Plugins';
|
1133 |
-
?>
|
1134 |
-
<div class="clear"></div>
|
1135 |
-
|
1136 |
-
<div class="ws_hint" id="<?php echo esc_attr($hint_id); ?>">
|
1137 |
-
<div class="ws_hint_close" title="Close">x</div>
|
1138 |
-
<div class="ws_hint_content">
|
1139 |
-
<strong>Upgrade to Pro:</strong>
|
1140 |
-
<ul>
|
1141 |
-
<li><?php echo $selected_variation; ?></li>
|
1142 |
-
<li>Drag items between menu levels.</li>
|
1143 |
-
<li>Menu export & import.</li>
|
1144 |
-
</ul>
|
1145 |
-
<a href="<?php echo esc_attr($pro_version_link); ?>" target="_blank">Learn more</a>
|
1146 |
-
</div>
|
1147 |
-
</div>
|
1148 |
-
<?php
|
1149 |
-
endif;
|
1150 |
-
?>
|
1151 |
|
1152 |
-
|
|
|
1153 |
|
1154 |
-
|
1155 |
-
|
1156 |
-
|
1157 |
-
|
1158 |
-
$capSelector[] = '<optgroup label="Roles">';
|
1159 |
-
foreach($all_roles as $role_id => $role_name){
|
1160 |
-
$capSelector[] = sprintf(
|
1161 |
-
'<option value="%s">%s</option>',
|
1162 |
-
esc_attr($role_id),
|
1163 |
-
$role_name
|
1164 |
-
);
|
1165 |
-
}
|
1166 |
-
$capSelector[] = '</optgroup>';
|
1167 |
-
|
1168 |
-
$capSelector[] = '<optgroup label="Capabilities">';
|
1169 |
-
foreach($all_capabilities as $cap){
|
1170 |
-
$capSelector[] = sprintf(
|
1171 |
-
'<option value="%s">%s</option>',
|
1172 |
-
esc_attr($cap),
|
1173 |
-
$cap
|
1174 |
-
);
|
1175 |
-
}
|
1176 |
-
$capSelector[] = '</optgroup>';
|
1177 |
-
$capSelector[] = '</select>';
|
1178 |
-
|
1179 |
-
echo implode("\n", $capSelector);
|
1180 |
-
|
1181 |
-
//Create a pop-up page selector
|
1182 |
-
$pageSelector = array('<select id="ws_page_selector" class="ws_dropdown" size="10">');
|
1183 |
-
foreach($default_menu as $toplevel){
|
1184 |
-
if ( $toplevel['separator'] ) continue;
|
1185 |
-
|
1186 |
-
$top_title = strip_tags( preg_replace('@<span[^>]*>.*</span>@i', '', $this->get_menu_field($toplevel, 'menu_title')) );
|
1187 |
-
|
1188 |
-
if ( empty($toplevel['items'])) {
|
1189 |
-
//This menu has no items, so it can only link to itself
|
1190 |
-
$pageSelector[] = sprintf(
|
1191 |
-
'<option value="%s">%s -> %s</option>',
|
1192 |
-
esc_attr($this->get_menu_field($toplevel, 'file')),
|
1193 |
-
$top_title,
|
1194 |
-
$top_title
|
1195 |
-
);
|
1196 |
-
} else {
|
1197 |
-
//When a menu has some items, it's own URL is ignored by WP and the first item is used instead.
|
1198 |
-
foreach($toplevel['items'] as $subitem){
|
1199 |
-
$sub_title = strip_tags( preg_replace('@<span[^>]*>.*</span>@i', '', $this->get_menu_field($subitem, 'menu_title')) );
|
1200 |
-
|
1201 |
-
$pageSelector[] = sprintf(
|
1202 |
-
'<option value="%s">%s -> %s</option>',
|
1203 |
-
esc_attr($this->get_menu_field($subitem, 'file')),
|
1204 |
-
$top_title,
|
1205 |
-
$sub_title
|
1206 |
-
);
|
1207 |
-
}
|
1208 |
-
}
|
1209 |
-
}
|
1210 |
-
|
1211 |
-
$pageSelector[] = '</select>';
|
1212 |
-
echo implode("\n", $pageSelector);
|
1213 |
-
?>
|
1214 |
-
|
1215 |
-
<!-- Menu icon selector widget -->
|
1216 |
-
<div id="ws_icon_selector" style="display: none;">
|
1217 |
-
<?php
|
1218 |
-
//Let the user select a custom icon via the media uploader.
|
1219 |
-
//We only support the new WP 3.5+ media API. Hence the function_exists() check.
|
1220 |
-
if ( function_exists('wp_enqueue_media') ):
|
1221 |
-
?>
|
1222 |
-
<input type="button" class="button"
|
1223 |
-
id="ws_choose_icon_from_media"
|
1224 |
-
title="Upload an image or choose one from your media library"
|
1225 |
-
value="Choose Icon">
|
1226 |
-
<div class="clear"></div>
|
1227 |
-
<?php
|
1228 |
-
endif;
|
1229 |
-
?>
|
1230 |
-
|
1231 |
-
<?php
|
1232 |
-
$defaultWpIcons = array(
|
1233 |
-
'generic', 'dashboard', 'post', 'media', 'links', 'page', 'comments',
|
1234 |
-
'appearance', 'plugins', 'users', 'tools', 'settings', 'site',
|
1235 |
-
);
|
1236 |
-
foreach($defaultWpIcons as $icon) {
|
1237 |
-
printf(
|
1238 |
-
'<div class="ws_icon_option" title="%1$s" data-icon-class="menu-icon-%2$s">
|
1239 |
-
<div class="ws_icon_image icon16 icon-%2$s"><br></div>
|
1240 |
-
</div>',
|
1241 |
-
esc_attr(ucwords($icon)),
|
1242 |
-
$icon
|
1243 |
-
);
|
1244 |
}
|
1245 |
|
1246 |
-
|
1247 |
-
|
1248 |
-
|
1249 |
-
|
1250 |
-
|
1251 |
-
'
|
1252 |
-
|
1253 |
-
</div>',
|
1254 |
-
esc_attr($icon)
|
1255 |
);
|
1256 |
-
}
|
1257 |
-
?>
|
1258 |
-
<div class="ws_icon_option ws_custom_image_icon" title="Custom image" style="display: none;">
|
1259 |
-
<img src="<?php echo esc_attr(admin_url('images/loading.gif')); ?>" alt="Custom image">
|
1260 |
-
</div>
|
1261 |
-
<div class="clear"></div>
|
1262 |
-
</div>
|
1263 |
|
1264 |
-
|
1265 |
-
|
1266 |
-
|
1267 |
-
|
1268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1269 |
}
|
1270 |
-
|
1271 |
-
|
1272 |
-
</span>
|
1273 |
|
1274 |
-
|
|
|
|
|
|
|
|
|
1275 |
|
1276 |
-
|
1277 |
-
|
|
|
|
|
1278 |
|
1279 |
-
|
|
|
1280 |
|
1281 |
-
|
|
|
1282 |
|
1283 |
-
|
1284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1285 |
|
1286 |
-
|
1287 |
-
|
1288 |
|
1289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1290 |
|
1291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1292 |
|
1293 |
-
|
1294 |
-
|
1295 |
-
|
1296 |
-
|
|
|
|
|
|
|
|
|
|
|
1297 |
}
|
1298 |
|
1299 |
-
|
1300 |
-
|
1301 |
-
|
1302 |
-
|
1303 |
-
|
1304 |
-
|
1305 |
-
|
1306 |
-
|
1307 |
-
|
1308 |
-
|
1309 |
-
|
1310 |
-
|
1311 |
-
|
|
|
|
|
1312 |
}
|
1313 |
-
|
1314 |
-
|
1315 |
-
|
1316 |
-
|
1317 |
-
|
1318 |
-
}
|
1319 |
}
|
1320 |
-
|
1321 |
-
|
1322 |
-
|
1323 |
-
|
1324 |
-
|
1325 |
-
|
1326 |
-
|
1327 |
-
'manage_network_options' => 1,
|
1328 |
-
'manage_network_plugins' => 1,
|
1329 |
-
);
|
1330 |
-
$capabilities = array_merge($capabilities, $multisite_caps);
|
1331 |
-
|
1332 |
-
return $capabilities;
|
1333 |
}
|
1334 |
-
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
1338 |
-
|
1339 |
-
|
1340 |
-
|
1341 |
-
|
1342 |
-
|
1343 |
-
|
1344 |
-
|
1345 |
-
|
1346 |
-
|
|
|
1347 |
}
|
1348 |
-
|
1349 |
-
foreach($
|
1350 |
-
$
|
1351 |
}
|
1352 |
-
|
1353 |
-
return $
|
1354 |
}
|
1355 |
|
1356 |
/**
|
@@ -1371,25 +1455,6 @@ window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
|
1371 |
return $allcaps;
|
1372 |
}
|
1373 |
|
1374 |
-
/**
|
1375 |
-
* Output the "Upgrade to Pro" message
|
1376 |
-
*
|
1377 |
-
* @return void
|
1378 |
-
*/
|
1379 |
-
function print_upgrade_notice(){
|
1380 |
-
?>
|
1381 |
-
<script type="text/javascript">
|
1382 |
-
(function($){
|
1383 |
-
$('#screen-meta-links').append(
|
1384 |
-
'<div id="ws-pro-version-notice">' +
|
1385 |
-
'<a href="http://adminmenueditor.com/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=top_upgrade_link&utm_campaign=Plugins" id="ws-pro-version-notice-link" class="show-settings" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
|
1386 |
-
'</div>'
|
1387 |
-
);
|
1388 |
-
})(jQuery);
|
1389 |
-
</script>
|
1390 |
-
<?php
|
1391 |
-
}
|
1392 |
-
|
1393 |
/**
|
1394 |
* AJAX callback for saving screen options (whether to show or to hide advanced menu options).
|
1395 |
*
|
@@ -1399,7 +1464,7 @@ window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
|
1399 |
* @return void
|
1400 |
*/
|
1401 |
function ajax_save_screen_options(){
|
1402 |
-
if (
|
1403 |
die( $this->json_encode( array(
|
1404 |
'error' => "You're not allowed to do that!"
|
1405 |
)));
|
@@ -1431,7 +1496,7 @@ window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
|
1431 |
|
1432 |
$defaults = array(
|
1433 |
'ws_sidebar_pro_ad' => true,
|
1434 |
-
|
1435 |
'ws_hint_menu_permissions' => true,
|
1436 |
);
|
1437 |
|
@@ -1448,22 +1513,216 @@ window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
|
1448 |
* would not be highlighted properly when the user visits them.
|
1449 |
*/
|
1450 |
public function enqueue_menu_fix_script() {
|
1451 |
-
|
1452 |
'ame-menu-fix',
|
1453 |
plugins_url('js/menu-highlight-fix.js', $this->plugin_file),
|
1454 |
array('jquery'),
|
1455 |
-
'20120915',
|
1456 |
true
|
1457 |
);
|
1458 |
}
|
1459 |
-
|
1460 |
/**
|
1461 |
-
*
|
1462 |
-
*
|
1463 |
-
* @return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1464 |
*/
|
1465 |
-
function
|
1466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1467 |
}
|
1468 |
|
1469 |
public function display_survey_notice() {
|
@@ -1480,8 +1739,15 @@ window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
|
1480 |
$display_notice = $display_notice && ((time() - $this->options['first_install_time']) > $minimum_usage_period);
|
1481 |
}
|
1482 |
|
1483 |
-
//Only display the notice on the Menu Editor page.
|
1484 |
$display_notice = $display_notice && isset($this->get['page']) && ($this->get['page'] == 'menu_editor');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1485 |
|
1486 |
if ( $display_notice ) {
|
1487 |
$free_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dERyeDk0OWhlbkxYcEY4QTNaMnlTQUE6MQ';
|
@@ -1497,8 +1763,8 @@ window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
|
1497 |
printf(
|
1498 |
'<div class="updated">
|
1499 |
<p><strong>Help improve Admin Menu Editor - take the user survey!</strong></p>
|
1500 |
-
<p
|
1501 |
-
<p
|
1502 |
</div>',
|
1503 |
esc_attr($survey_url),
|
1504 |
esc_attr($hide_url)
|
@@ -1522,8 +1788,168 @@ window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
|
1522 |
}
|
1523 |
}
|
1524 |
|
1525 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1526 |
|
1527 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1528 |
|
1529 |
-
|
8 |
);
|
9 |
}
|
10 |
|
|
|
11 |
$thisDirectory = dirname(__FILE__);
|
12 |
require $thisDirectory . '/shadow_plugin_framework.php';
|
13 |
+
require $thisDirectory . '/role-utils.php';
|
14 |
require $thisDirectory . '/menu-item.php';
|
15 |
+
require $thisDirectory . '/menu.php';
|
16 |
+
require $thisDirectory . '/auto-versioning.php';
|
17 |
|
18 |
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
19 |
+
const WPML_CONTEXT = 'admin-menu-editor menu texts';
|
20 |
+
|
21 |
+
private $plugin_db_version = 140;
|
22 |
|
23 |
+
/** @var array The default WordPress menu, before display-specific filtering. */
|
24 |
+
protected $default_wp_menu;
|
25 |
+
/** @var array The default WordPress submenu. */
|
26 |
+
protected $default_wp_submenu;
|
27 |
+
|
28 |
+
/**
|
29 |
+
* We also keep track of the final, ready-for-display version of the default WP menu
|
30 |
+
* and submenu. These values are captured *just* before the admin menu HTML is output
|
31 |
+
* by _wp_menu_output() in /wp-admin/menu-header.php, and are restored afterwards.
|
32 |
+
*/
|
33 |
+
private $old_wp_menu;
|
34 |
+
private $old_wp_submenu;
|
35 |
|
36 |
+
private $title_lookups = array(); //A list of page titles indexed by $item['file']. Used to
|
37 |
//fix the titles of moved plugin pages.
|
38 |
+
private $reverse_item_lookup = array(); //Contains the final (merged & filtered) list of admin menu items,
|
39 |
+
//indexed by URL.
|
40 |
+
|
41 |
+
/**
|
42 |
+
* @var array List of per-URL capabilities, indexed by priority. Used while merging and
|
43 |
+
* building the final admin menu.
|
44 |
+
*/
|
45 |
+
private $page_access_lookup = array();
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @var array A log of menu access checks.
|
49 |
+
*/
|
50 |
+
private $security_log = array();
|
51 |
+
|
52 |
+
/**
|
53 |
+
* @var array The current custom menu with defaults merged in.
|
54 |
+
*/
|
55 |
+
private $merged_custom_menu = null;
|
56 |
+
|
57 |
+
/**
|
58 |
+
* @var array The custom menu in WP-compatible format (top-level).
|
59 |
+
*/
|
60 |
+
private $custom_wp_menu = null;
|
61 |
+
|
62 |
+
/**
|
63 |
+
* @var array The custom menu in WP-compatible format (sub-menu).
|
64 |
+
*/
|
65 |
+
private $custom_wp_submenu = null;
|
66 |
+
|
67 |
+
private $item_templates = array(); //A lookup list of default menu items, used as templates for the custom menu.
|
68 |
+
|
69 |
+
private $cached_custom_menu = null; //Cached, non-merged version of the custom menu. Used by load_custom_menu().
|
70 |
+
private $cached_virtual_caps = null;//List of virtual caps. Used by get_virtual_caps().
|
71 |
|
72 |
//Our personal copy of the request vars, without any "magic quotes".
|
73 |
private $post = array();
|
74 |
private $get = array();
|
75 |
|
76 |
function init(){
|
77 |
+
$this->sitewide_options = true;
|
78 |
+
|
|
|
|
|
|
|
|
|
79 |
//Set some plugin-specific options
|
80 |
if ( empty($this->option_name) ){
|
81 |
$this->option_name = 'ws_menu_editor';
|
82 |
}
|
83 |
$this->defaults = array(
|
84 |
'hide_advanced_settings' => true,
|
85 |
+
'custom_menu' => null,
|
|
|
86 |
'first_install_time' => null,
|
87 |
+
'display_survey_notice' => true,
|
88 |
+
'plugin_db_version' => 0,
|
89 |
+
'security_logging_enabled' => false,
|
90 |
+
|
91 |
+
'menu_config_scope' => ($this->is_super_plugin() || !is_multisite()) ? 'global' : 'site',
|
92 |
+
|
93 |
+
//super_admin, specific_user, or a capability.
|
94 |
+
'plugin_access' => $this->is_super_plugin() ? 'super_admin' : 'manage_options',
|
95 |
+
//The ID of the user who is allowed to use this plugin. Only used when plugin_access == specific_user.
|
96 |
+
'allowed_user_id' => null,
|
97 |
+
//The user who can see this plugin on the "Plugins" page. By default all admins can see it.
|
98 |
+
'plugins_page_allowed_user_id' => null,
|
99 |
);
|
100 |
$this->serialize_with_json = false; //(Don't) store the options in JSON format
|
101 |
|
104 |
$this->magic_hooks = true;
|
105 |
$this->magic_hook_priority = 99999;
|
106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
//AJAXify screen options
|
108 |
+
add_action('wp_ajax_ws_ame_save_screen_options', array(&$this,'ajax_save_screen_options'));
|
109 |
|
110 |
//AJAXify hints
|
111 |
add_action('wp_ajax_ws_ame_hide_hint', array($this, 'ajax_hide_hint'));
|
113 |
//Make sure we have access to the original, un-mangled request data.
|
114 |
//This is necessary because WordPress will stupidly apply "magic quotes"
|
115 |
//to the request vars even if this PHP misfeature is disabled.
|
116 |
+
$this->capture_request_vars();
|
117 |
|
118 |
add_action('admin_enqueue_scripts', array($this, 'enqueue_menu_fix_script'));
|
119 |
|
120 |
+
//Enqueue miscellaneous helper scripts and styles.
|
121 |
+
add_action('admin_enqueue_scripts', array($this, 'enqueue_helper_scripts'));
|
122 |
+
add_action('admin_print_styles', array($this, 'enqueue_helper_styles'));
|
123 |
+
|
124 |
//User survey
|
125 |
add_action('admin_notices', array($this, 'display_survey_notice'));
|
126 |
}
|
127 |
+
|
128 |
function init_finish() {
|
129 |
parent::init_finish();
|
130 |
+
$should_save_options = false;
|
131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
//If we have no stored settings for this version of the plugin, try importing them
|
133 |
//from other versions (i.e. the free or the Pro version).
|
134 |
if ( !$this->load_options() ){
|
135 |
$this->import_settings();
|
136 |
+
$should_save_options = true;
|
137 |
+
}
|
138 |
+
|
139 |
+
//Track first install time.
|
140 |
+
if ( !isset($this->options['first_install_time']) ) {
|
141 |
+
$this->options['first_install_time'] = time();
|
142 |
+
$should_save_options = true;
|
143 |
+
}
|
144 |
+
|
145 |
+
if ( $this->options['plugin_db_version'] < $this->plugin_db_version ) {
|
146 |
+
/* Put any activation code here. */
|
147 |
+
|
148 |
+
$this->options['plugin_db_version'] = $this->plugin_db_version;
|
149 |
+
$should_save_options = true;
|
150 |
+
}
|
151 |
+
|
152 |
+
if ( $should_save_options ) {
|
153 |
+
$this->save_options();
|
154 |
}
|
155 |
|
156 |
+
//This is here and not in init() because it relies on $options being initialized.
|
157 |
+
if ( $this->options['security_logging_enabled'] ) {
|
158 |
+
add_action('admin_notices', array($this, 'display_security_log'));
|
159 |
+
}
|
160 |
}
|
161 |
+
|
162 |
/**
|
163 |
* Import settings from a different version of the plugin.
|
164 |
*
|
171 |
return true;
|
172 |
}
|
173 |
}
|
|
|
174 |
return false;
|
175 |
}
|
176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
/**
|
178 |
* Create a configuration page and load the custom menu
|
179 |
*
|
181 |
*/
|
182 |
function hook_admin_menu(){
|
183 |
global $menu, $submenu;
|
184 |
+
|
185 |
//Menu reset (for emergencies). Executed by accessing http://example.com/wp-admin/?reset_admin_menu=1
|
186 |
$reset_requested = isset($this->get['reset_admin_menu']) && $this->get['reset_admin_menu'];
|
187 |
if ( $reset_requested && $this->current_user_can_edit_menu() ){
|
188 |
+
$this->set_custom_menu(null);
|
|
|
189 |
}
|
190 |
|
191 |
//The menu editor is only visible to users with the manage_options privilege.
|
192 |
//Or, if the plugin is installed in mu-plugins, only to the site administrator(s).
|
193 |
if ( $this->current_user_can_edit_menu() ){
|
194 |
+
$this->log_security_note('Current user can edit the admin menu.');
|
195 |
+
|
196 |
$page = add_options_page(
|
197 |
apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'),
|
198 |
apply_filters('admin_menu_editor-self_menu_title', 'Menu Editor'),
|
199 |
+
apply_filters('admin_menu_editor_capability', 'manage_options'),
|
200 |
'menu_editor',
|
201 |
array(&$this, 'page_menu_editor')
|
202 |
);
|
203 |
//Output our JS & CSS on that page only
|
204 |
add_action("admin_print_scripts-$page", array(&$this, 'enqueue_scripts'));
|
205 |
add_action("admin_print_styles-$page", array(&$this, 'enqueue_styles'));
|
206 |
+
|
207 |
+
//Compatibility fix for All In One Event Calendar; see the callback for details.
|
208 |
+
add_action("admin_print_scripts-$page", array($this, 'dequeue_ai1ec_scripts'));
|
209 |
+
|
210 |
//Compatibility fix for Participants Database.
|
211 |
add_action("admin_print_scripts-$page", array($this, 'dequeue_pd_scripts'));
|
212 |
+
|
213 |
+
//Experimental compatibility fix for Ultimate TinyMCE
|
214 |
+
add_action("admin_print_scripts-$page", array($this, 'remove_ultimate_tinymce_qtags'));
|
215 |
+
|
216 |
//Make a placeholder for our screen options (hacky)
|
217 |
+
add_meta_box("ws-ame-screen-options", "[AME placeholder]", '__return_false', $page);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
}
|
219 |
|
220 |
//Store the "original" menus for later use in the editor
|
221 |
$this->default_wp_menu = $menu;
|
222 |
$this->default_wp_submenu = $submenu;
|
223 |
|
224 |
+
//Generate item templates from the default menu.
|
225 |
+
$this->item_templates = $this->build_templates($this->default_wp_menu, $this->default_wp_submenu);
|
226 |
+
|
227 |
//Is there a custom menu to use?
|
228 |
+
$custom_menu = $this->load_custom_menu();
|
229 |
+
if ( $custom_menu !== null ){
|
|
|
|
|
|
|
|
|
|
|
230 |
//Merge in data from the default menu
|
231 |
+
$custom_menu['tree'] = $this->menu_merge($custom_menu['tree']);
|
232 |
+
|
233 |
+
//Save the merged menu for later - the editor page will need it
|
234 |
+
$this->merged_custom_menu = $custom_menu;
|
235 |
+
|
236 |
+
//Convert our custom menu to the $menu + $submenu structure used by WP.
|
237 |
+
//Note: This method sets up multiple internal fields and may cause side-effects.
|
238 |
+
$this->build_custom_wp_menu($this->merged_custom_menu['tree']);
|
239 |
+
|
240 |
+
if ( !$this->user_can_access_current_page() ) {
|
241 |
+
$this->log_security_note('DENY access.');
|
242 |
+
$message = 'You do not have sufficient permissions to access this admin page.';
|
243 |
+
if ( $this->options['security_logging_enabled'] ) {
|
244 |
+
$message .= '<p><strong>Admin Menu Editor security log</strong></p>';
|
245 |
+
$message .= $this->get_formatted_security_log();
|
246 |
+
}
|
247 |
+
wp_die($message);
|
248 |
+
} else {
|
249 |
+
$this->log_security_note('ALLOW access.');
|
250 |
+
}
|
251 |
+
|
252 |
+
//Replace the admin menu just before it is displayed and restore it afterwards.
|
253 |
+
//The fact that replace_wp_menu() is attached to the 'parent_file' hook is incidental;
|
254 |
+
//there just wasn't any other, more suitable hook available.
|
255 |
+
add_filter('parent_file', array($this, 'replace_wp_menu'));
|
256 |
+
add_action('adminmenu', array($this, 'restore_wp_menu'));
|
257 |
+
|
258 |
+
//A compatibility hack for Ozh's Admin Drop Down Menu. Make sure it also sees the modified menu.
|
259 |
+
$ozh_adminmenu_priority = has_action('in_admin_header', 'wp_ozh_adminmenu');
|
260 |
+
if ( $ozh_adminmenu_priority !== false ) {
|
261 |
+
add_action('in_admin_header', array($this, 'replace_wp_menu'), $ozh_adminmenu_priority - 1);
|
262 |
+
add_action('in_admin_header', array($this, 'restore_wp_menu'), $ozh_adminmenu_priority + 1);
|
263 |
+
}
|
264 |
}
|
265 |
}
|
266 |
|
267 |
/**
|
268 |
+
* Replace the current WP menu with our custom one.
|
269 |
*
|
270 |
+
* @param string $parent_file Ignored. Required because this method is a hook for the 'parent_file' filter.
|
271 |
+
* @return string Returns the $parent_file argument.
|
272 |
*/
|
273 |
+
public function replace_wp_menu($parent_file = '') {
|
274 |
+
global $menu, $submenu;
|
275 |
+
|
276 |
+
$this->old_wp_menu = $menu;
|
277 |
+
$this->old_wp_submenu = $submenu;
|
278 |
+
|
279 |
+
$menu = $this->custom_wp_menu;
|
280 |
+
$submenu = $this->custom_wp_submenu;
|
281 |
+
list($menu, $submenu) = $this->filter_menu($menu, $submenu);
|
282 |
+
|
283 |
+
return $parent_file;
|
284 |
}
|
285 |
|
286 |
/**
|
287 |
+
* Restore the default WordPress menu that was replaced using replace_wp_menu().
|
288 |
*
|
289 |
+
* @return void
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
*/
|
291 |
+
public function restore_wp_menu() {
|
292 |
+
global $menu, $submenu;
|
293 |
+
$menu = $this->old_wp_menu;
|
294 |
+
$submenu = $this->old_wp_submenu;
|
|
|
295 |
}
|
296 |
+
|
297 |
/**
|
298 |
+
* Filter a menu so that it can be handed to _wp_menu_output(). This method basically
|
299 |
+
* emulates the filtering that WordPress does in /wp-admin/includes/menu.php, with a few
|
300 |
+
* additions of our own.
|
301 |
+
*
|
302 |
+
* - Removes inaccessible items and superfluous separators.
|
303 |
+
*
|
304 |
+
* - Sets accessible items to a capability that the user is guaranteed to have to prevent
|
305 |
+
* _wp_menu_output() from choking on plugin-specific capabilities like "cap1,cap2+not:cap3".
|
306 |
+
*
|
307 |
+
* - Adds position-dependent CSS classes.
|
308 |
+
*
|
309 |
+
* @param array $menu
|
310 |
+
* @param array $submenu
|
311 |
+
* @return array An array with two items - the filtered menu and submenu.
|
312 |
*/
|
313 |
+
private function filter_menu($menu, $submenu) {
|
314 |
+
global $_wp_menu_nopriv; //Caution: Modifying this array could lead to unexpected consequences.
|
315 |
+
|
316 |
+
//Remove sub-menus which the user shouldn't be able to access,
|
317 |
+
//and ensure the rest are visible.
|
318 |
+
foreach ($submenu as $parent => $items) {
|
319 |
+
foreach ($items as $index => $data) {
|
320 |
+
if ( ! $this->current_user_can($data[1]) ) {
|
321 |
+
unset($submenu[$parent][$index]);
|
322 |
+
$_wp_submenu_nopriv[$parent][$data[2]] = true;
|
323 |
+
} else {
|
324 |
+
//The menu might be set to some kind of special capability that is only valid
|
325 |
+
//within this plugin and not WP in general. Ensure WP doesn't choke on it.
|
326 |
+
//(This is safe - we'll double-check the caps when the user tries to access a page.)
|
327 |
+
$submenu[$parent][$index][1] = 'exist'; //All users have the 'exist' cap.
|
328 |
+
}
|
329 |
+
}
|
330 |
+
|
331 |
+
if ( empty($submenu[$parent]) ) {
|
332 |
+
unset($submenu[$parent]);
|
333 |
}
|
334 |
}
|
|
|
|
|
|
|
|
|
335 |
|
336 |
+
//Remove menus that have no accessible sub-menus and require privileges that the user does not have.
|
337 |
+
//Ensure the rest are visible. Run re-parent loop again.
|
338 |
+
foreach ( $menu as $id => $data ) {
|
339 |
+
if ( ! $this->current_user_can($data[1]) ) {
|
340 |
+
$_wp_menu_nopriv[$data[2]] = true;
|
341 |
+
} else {
|
342 |
+
$menu[$id][1] = 'exist';
|
343 |
+
}
|
344 |
+
|
345 |
+
//If there is only one submenu and it is has same destination as the parent,
|
346 |
+
//remove the submenu.
|
347 |
+
if ( ! empty( $submenu[$data[2]] ) && 1 == count ( $submenu[$data[2]] ) ) {
|
348 |
+
$subs = $submenu[$data[2]];
|
349 |
+
$first_sub = array_shift($subs);
|
350 |
+
if ( $data[2] == $first_sub[2] ) {
|
351 |
+
unset( $submenu[$data[2]] );
|
352 |
}
|
353 |
+
}
|
354 |
|
355 |
+
//If submenu is empty...
|
356 |
+
if ( empty($submenu[$data[2]]) ) {
|
357 |
+
// And user doesn't have privs, remove menu.
|
358 |
+
if ( isset( $_wp_menu_nopriv[$data[2]] ) ) {
|
359 |
+
unset($menu[$id]);
|
360 |
+
}
|
361 |
}
|
362 |
}
|
363 |
+
unset($id, $data, $subs, $first_sub);
|
364 |
+
|
365 |
+
//Remove any duplicated separators
|
366 |
+
$separator_found = false;
|
367 |
+
foreach ( $menu as $id => $data ) {
|
368 |
+
if ( 0 == strcmp('wp-menu-separator', $data[4] ) ) {
|
369 |
+
if ($separator_found) {
|
370 |
+
unset($menu[$id]);
|
371 |
+
}
|
372 |
+
$separator_found = true;
|
373 |
+
} else {
|
374 |
+
$separator_found = false;
|
375 |
+
}
|
376 |
+
}
|
377 |
+
unset($id, $data);
|
378 |
|
379 |
+
//Remove the last menu item if it is a separator.
|
380 |
+
$last_menu_key = array_keys( $menu );
|
381 |
+
$last_menu_key = array_pop( $last_menu_key );
|
382 |
+
if (!empty($menu) && 'wp-menu-separator' == $menu[$last_menu_key][4]) {
|
383 |
+
unset($menu[$last_menu_key]);
|
384 |
+
}
|
385 |
+
unset( $last_menu_key );
|
|
|
|
|
386 |
|
387 |
+
//Add display-specific classes like "menu-top-first" and others.
|
388 |
+
$menu = add_menu_classes($menu);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
389 |
|
390 |
+
return array($menu, $submenu);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
391 |
}
|
392 |
|
393 |
+
|
394 |
+
/**
|
395 |
+
* Add the JS required by the editor to the page header
|
396 |
+
*
|
397 |
+
* @return void
|
398 |
+
*/
|
399 |
+
function enqueue_scripts(){
|
400 |
+
//jQuery JSON plugin
|
401 |
+
wp_register_auto_versioned_script('jquery-json', plugins_url('js/jquery.json.js', $this->plugin_file), array('jquery'));
|
402 |
+
//jQuery sort plugin
|
403 |
+
wp_register_auto_versioned_script('jquery-sort', plugins_url('js/jquery.sort.js', $this->plugin_file), array('jquery'));
|
404 |
+
//qTip2 - jQuery tooltip plugin
|
405 |
+
wp_register_auto_versioned_script('jquery-qtip', plugins_url('js/jquery.qtip.min.js', $this->plugin_file), array('jquery'));
|
406 |
+
//jQuery Form plugin. This is a more recent version than the one included with WP.
|
407 |
+
wp_register_auto_versioned_script('ame-jquery-form', plugins_url('js/jquery.form.js', $this->plugin_file), array('jquery'));
|
408 |
+
|
409 |
+
//Editor's scripts
|
410 |
+
wp_register_auto_versioned_script(
|
411 |
+
'menu-editor',
|
412 |
+
plugins_url('js/menu-editor.js', $this->plugin_file),
|
413 |
+
array(
|
414 |
+
'jquery', 'jquery-ui-sortable', 'jquery-ui-dialog',
|
415 |
+
'ame-jquery-form', 'jquery-ui-droppable', 'jquery-qtip',
|
416 |
+
'jquery-sort', 'jquery-json'
|
417 |
+
)
|
418 |
+
);
|
419 |
+
|
420 |
+
//Add scripts to our editor page, but not the settings sub-section
|
421 |
+
//that shares the same page slug. Some of the scripts would crash otherwise.
|
422 |
+
if ( !$this->is_editor_page() ) {
|
423 |
+
return;
|
424 |
}
|
425 |
|
426 |
+
wp_enqueue_script('menu-editor');
|
427 |
+
|
428 |
+
//We use WordPress media uploader to let the user upload custom menu icons (WP 3.5+).
|
429 |
+
if ( function_exists('wp_enqueue_media') ) {
|
430 |
+
wp_enqueue_media();
|
|
|
|
|
|
|
|
|
431 |
}
|
432 |
|
433 |
+
//Remove the default jQuery Form plugin to prevent conflicts with our custom version.
|
434 |
+
wp_dequeue_script('jquery-form');
|
435 |
|
436 |
+
//Actors (roles and users) are used in the permissions UI, so we need to pass them along.
|
437 |
+
$actors = array();
|
438 |
+
$roles = array();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
439 |
|
440 |
+
$wp_roles = ameRoleUtils::get_roles();
|
441 |
+
foreach($wp_roles->roles as $role_id => $role) {
|
442 |
+
$actors['role:' . $role_id] = $role['name'];
|
443 |
+
$role['capabilities'] = $this->castValuesToBool($role['capabilities']);
|
444 |
+
$roles[$role_id] = $role;
|
445 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
446 |
|
447 |
+
if ( is_multisite() && is_super_admin() ) {
|
448 |
+
$actors['special:super_admin'] = 'Super Admin';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
449 |
}
|
450 |
|
451 |
+
//Known users. Right now, this is limited to the current user only.
|
452 |
+
$users = array();
|
|
|
453 |
|
454 |
+
$current_user = wp_get_current_user();
|
455 |
+
$users[$current_user->get('user_login')] = array(
|
456 |
+
'user_login' => $current_user->get('user_login'),
|
457 |
+
'id' => $current_user->ID,
|
458 |
+
'roles' => array_values($current_user->roles),
|
459 |
+
'capabilities' => $this->castValuesToBool($current_user->caps),
|
460 |
+
'is_super_admin' => is_multisite() && is_super_admin(),
|
461 |
+
);
|
462 |
|
463 |
+
$actors['user:' . $current_user->get('user_login')] = sprintf(
|
464 |
+
'Current user (%s)',
|
465 |
+
$current_user->get('user_login')
|
466 |
+
);
|
467 |
+
//Note: Users do NOT get added to the actor list because that feature
|
468 |
+
//is not fully implemented.
|
469 |
|
470 |
+
//The editor will need access to some of the plugin data and WP data.
|
471 |
+
wp_localize_script(
|
472 |
+
'menu-editor',
|
473 |
+
'wsEditorData',
|
474 |
+
array(
|
475 |
+
'imagesUrl' => plugins_url('images', $this->plugin_file),
|
476 |
+
'adminAjaxUrl' => admin_url('admin-ajax.php'),
|
477 |
+
'hideAdvancedSettings' => (boolean)$this->options['hide_advanced_settings'],
|
478 |
+
'hideAdvancedSettingsNonce' => wp_create_nonce('ws_ame_save_screen_options'),
|
479 |
+
'captionShowAdvanced' => 'Show advanced options',
|
480 |
+
'captionHideAdvanced' => 'Hide advanced options',
|
481 |
+
'wsMenuEditorPro' => false, //Will be overwritten if extras are loaded
|
482 |
+
'menuFormatName' => ameMenu::format_name,
|
483 |
+
'menuFormatVersion' => ameMenu::format_version,
|
484 |
+
|
485 |
+
'blankMenuItem' => ameMenuItem::blank_menu(),
|
486 |
+
'itemTemplates' => $this->item_templates,
|
487 |
+
'customItemTemplate' => array(
|
488 |
+
'name' => '< Custom >',
|
489 |
+
'defaults' => ameMenuItem::custom_item_defaults(),
|
490 |
+
),
|
491 |
+
|
492 |
+
'actors' => $actors,
|
493 |
+
'roles' => $roles,
|
494 |
+
'users' => $users,
|
495 |
+
'currentUserLogin' => $current_user->get('user_login'),
|
496 |
+
'selectedActor' => isset($this->get['selected_actor']) ? strval($this->get['selected_actor']) : null,
|
497 |
|
498 |
+
'showHints' => $this->get_hint_visibility(),
|
499 |
+
|
500 |
+
'isDemoMode' => defined('IS_DEMO_MODE'),
|
501 |
+
'isMasterMode' => defined('IS_MASTER_MODE'),
|
502 |
+
)
|
503 |
+
);
|
504 |
+
}
|
505 |
+
|
506 |
+
/**
|
507 |
+
* Compatibility workaround for All In One Event Calendar 1.8.3-premium.
|
508 |
+
*
|
509 |
+
* The event calendar plugin is known to crash Admin Menu Editor Pro 1.40. The exact cause
|
510 |
+
* of the crash is unknown, but we can prevent it by removing AIOEC scripts from the menu
|
511 |
+
* editor page.
|
512 |
+
*
|
513 |
+
* This should not affect the functionality of the event calendar plugin. The scripts
|
514 |
+
* in question don't seem to do anything on pages not related to the event calendar. AIOEC
|
515 |
+
* just loads them indiscriminately on all pages.
|
516 |
+
*/
|
517 |
+
public function dequeue_ai1ec_scripts() {
|
518 |
+
wp_dequeue_script('ai1ec_requirejs');
|
519 |
+
wp_dequeue_script('ai1ec_common_backend');
|
520 |
+
wp_dequeue_script('ai1ec_add_new_event_require');
|
521 |
+
}
|
522 |
+
|
523 |
+
/**
|
524 |
+
* Compatibility workaround for Participants Database 1.4.5.2.
|
525 |
+
*
|
526 |
+
* Participants Database loads its settings JavaScript on every page in the "Settings" menu,
|
527 |
+
* not just its own. It doesn't bother to also load the script's dependencies, though, so
|
528 |
+
* the script crashes *and* it breaks the menu editor by way of collateral damage.
|
529 |
+
*
|
530 |
+
* Fix by forcibly removing the offending script from the queue.
|
531 |
+
*/
|
532 |
+
public function dequeue_pd_scripts() {
|
533 |
+
if ( is_plugin_active('participants-database/participants-database.php') ) {
|
534 |
+
wp_dequeue_script('settings_script');
|
535 |
}
|
536 |
+
}
|
537 |
|
538 |
+
public function remove_ultimate_tinymce_qtags() {
|
539 |
+
remove_action('admin_print_footer_scripts', 'jwl_ult_quicktags');
|
540 |
+
}
|
541 |
|
542 |
+
/**
|
543 |
+
* Add the editor's CSS file to the page header
|
544 |
+
*
|
545 |
+
* @return void
|
546 |
+
*/
|
547 |
+
function enqueue_styles(){
|
548 |
+
wp_enqueue_auto_versioned_style('jquery-qtip-syle', plugins_url('css/jquery.qtip.min.css', $this->plugin_file), array());
|
549 |
+
|
550 |
+
wp_register_auto_versioned_style('menu-editor-base-style', plugins_url('css/menu-editor.css', $this->plugin_file));
|
551 |
+
wp_register_auto_versioned_style(
|
552 |
+
'menu-editor-colours-classic',
|
553 |
+
plugins_url('css/style-classic.css', $this->plugin_file),
|
554 |
+
array('menu-editor-base-style')
|
555 |
+
);
|
556 |
+
wp_register_auto_versioned_style(
|
557 |
+
'menu-editor-colours-wp-gray',
|
558 |
+
plugins_url('css/style-wp-gray.css', $this->plugin_file),
|
559 |
+
array('menu-editor-base-style')
|
560 |
+
);
|
561 |
+
|
562 |
+
wp_enqueue_style('menu-editor-colours-classic');
|
563 |
}
|
564 |
+
|
565 |
+
/**
|
566 |
+
* Set and save a new custom menu for the current site.
|
567 |
+
*
|
568 |
+
* @param array|null $custom_menu
|
569 |
+
*/
|
570 |
+
function set_custom_menu($custom_menu) {
|
571 |
+
$previous_custom_menu = $this->load_custom_menu();
|
572 |
+
$this->update_wpml_strings($previous_custom_menu, $custom_menu);
|
573 |
+
|
574 |
+
if ( $this->should_use_site_specific_menu() ) {
|
575 |
+
$site_specific_options = get_option($this->option_name);
|
576 |
+
if ( !is_array($site_specific_options) ) {
|
577 |
+
$site_specific_options = array();
|
578 |
}
|
579 |
+
$site_specific_options['custom_menu'] = $custom_menu;
|
580 |
+
update_option($this->option_name, $site_specific_options);
|
|
|
|
|
|
|
581 |
} else {
|
582 |
+
$this->options['custom_menu'] = $custom_menu;
|
583 |
+
$this->save_options();
|
584 |
}
|
585 |
+
|
586 |
+
$this->cached_custom_menu = null;
|
587 |
+
$this->cached_virtual_caps = null;
|
588 |
}
|
589 |
|
590 |
+
/**
|
591 |
+
* Load the current custom menu for this site, if any.
|
592 |
+
*
|
593 |
+
* @return array|null Either a menu in the internal format, or NULL if there is no custom menu available.
|
594 |
+
*/
|
595 |
+
function load_custom_menu() {
|
596 |
+
if ( $this->cached_custom_menu !== null ) {
|
597 |
+
return $this->cached_custom_menu;
|
598 |
+
}
|
599 |
+
|
600 |
+
if ( $this->should_use_site_specific_menu() ) {
|
601 |
+
$site_specific_options = get_option($this->option_name, null);
|
602 |
+
if ( is_array($site_specific_options) && isset($site_specific_options['custom_menu']) ) {
|
603 |
+
$this->cached_custom_menu = ameMenu::load_array($site_specific_options['custom_menu']);
|
|
|
|
|
|
|
|
|
|
|
604 |
}
|
605 |
+
} else {
|
606 |
+
if ( empty($this->options['custom_menu']) ) {
|
607 |
+
return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
608 |
}
|
609 |
+
$this->cached_custom_menu = ameMenu::load_array($this->options['custom_menu']);
|
|
|
610 |
}
|
611 |
|
612 |
+
return $this->cached_custom_menu;
|
613 |
+
}
|
614 |
|
615 |
+
/**
|
616 |
+
* Determine if we should use a site-specific admin menu configuration
|
617 |
+
* for the current site, or fall back to the global config.
|
618 |
+
*
|
619 |
+
* @return bool True = use the site-specific config (if any), false = use the global config.
|
620 |
+
*/
|
621 |
+
protected function should_use_site_specific_menu() {
|
622 |
+
if ( !is_multisite() ) {
|
623 |
+
//If this is a single-site WP installation then there's really
|
624 |
+
//no difference between "site-specific" and "global".
|
625 |
+
return false;
|
626 |
+
}
|
627 |
+
return ($this->options['menu_config_scope'] === 'site');
|
628 |
}
|
629 |
|
630 |
+
/**
|
631 |
+
* Determine if the current user may use the menu editor.
|
632 |
+
*
|
633 |
+
* @return bool
|
634 |
+
*/
|
635 |
+
public function current_user_can_edit_menu(){
|
636 |
+
$access = $this->options['plugin_access'];
|
637 |
+
|
638 |
+
if ( $access === 'super_admin' ) {
|
639 |
+
return is_super_admin();
|
640 |
+
} else if ( $access === 'specific_user' ) {
|
641 |
+
return get_current_user_id() == $this->options['allowed_user_id'];
|
642 |
+
} else {
|
643 |
+
$capability = apply_filters('admin_menu_editor-capability', $access);
|
644 |
+
return current_user_can($capability);
|
645 |
}
|
|
|
646 |
}
|
647 |
|
648 |
+
/**
|
649 |
+
* Apply the custom page title, if any.
|
650 |
+
*
|
651 |
+
* This is a callback for the "admin_title" filter. It will change the browser window/tab
|
652 |
+
* title (i.e. <title>), but not the title displayed on the admin page itself.
|
653 |
+
*
|
654 |
+
* @param string $admin_title The current admin title (full).
|
655 |
+
* @param string $title The current page title.
|
656 |
+
* @return string New admin title.
|
657 |
+
*/
|
658 |
+
function hook_admin_title($admin_title, $title){
|
659 |
+
$item = $this->get_current_menu_item();
|
660 |
+
if ( $item === null ) {
|
661 |
+
return $admin_title;
|
|
|
|
|
|
|
|
|
|
|
662 |
}
|
663 |
+
|
664 |
+
//Check if the we have a custom title for this page.
|
665 |
+
$default_title = isset($item['defaults']['page_title']) ? $item['defaults']['page_title'] : '';
|
666 |
+
if ( !empty($item['page_title']) && $item['page_title'] != $default_title ) {
|
667 |
+
if ( empty($title) ) {
|
668 |
+
$admin_title = $item['page_title'] . $admin_title;
|
669 |
+
} else {
|
670 |
+
//Replace the first occurrence of the default title with the custom one.
|
671 |
+
$title_pos = strpos($admin_title, $title);
|
672 |
+
$admin_title = substr_replace($admin_title, $item['page_title'], $title_pos, strlen($title));
|
673 |
+
}
|
674 |
}
|
675 |
+
|
676 |
+
return $admin_title;
|
677 |
}
|
678 |
|
679 |
/**
|
680 |
+
* Populate a lookup array with default values (templates) from $menu and $submenu.
|
681 |
+
* Used later to merge a custom menu with the native WordPress menu structure.
|
|
|
682 |
*
|
683 |
+
* @param array $menu
|
684 |
+
* @param array $submenu
|
685 |
+
* @return array An array of menu templates and their default values.
|
|
|
686 |
*/
|
687 |
+
function build_templates($menu, $submenu){
|
688 |
+
$templates = array();
|
689 |
+
|
690 |
+
$name_lookup = array();
|
691 |
+
foreach($menu as $pos => $item){
|
692 |
+
$item = ameMenuItem::fromWpItem($item, $pos);
|
693 |
+
if ($item['separator']) {
|
694 |
+
continue;
|
695 |
+
}
|
696 |
+
|
697 |
+
$name = $this->sanitize_menu_title($item['menu_title']);
|
698 |
+
$name_lookup[$item['file']] = $name;
|
699 |
+
|
700 |
+
$templates[ameMenuItem::template_id($item)] = array(
|
701 |
+
'name' => $name,
|
702 |
+
'used' => false,
|
703 |
+
'defaults' => $item
|
704 |
+
);
|
705 |
+
}
|
706 |
+
|
707 |
+
foreach($submenu as $parent => $items){
|
708 |
+
//Skip sub-menus attached to non-existent parents. This should theoretically never happen,
|
709 |
+
//but a buggy plugin can cause such a situation.
|
710 |
+
if ( !isset($name_lookup[$parent]) ) {
|
711 |
+
continue;
|
712 |
+
}
|
713 |
+
|
714 |
+
foreach($items as $pos => $item){
|
715 |
+
$item = ameMenuItem::fromWpItem($item, $pos, $parent);
|
716 |
+
$templates[ameMenuItem::template_id($item)] = array(
|
717 |
+
'name' => $name_lookup[$parent] . ' -> ' . $this->sanitize_menu_title($item['menu_title']),
|
718 |
+
'used' => false,
|
719 |
+
'defaults' => $item
|
720 |
+
);
|
721 |
}
|
722 |
}
|
723 |
+
|
724 |
+
return $templates;
|
725 |
+
}
|
726 |
+
|
727 |
+
/**
|
728 |
+
* Sanitize a menu title for display.
|
729 |
+
* Removes HTML tags and update notification bubbles.
|
730 |
+
*
|
731 |
+
* @param string $title
|
732 |
+
* @return string
|
733 |
+
*/
|
734 |
+
private function sanitize_menu_title($title) {
|
735 |
+
return strip_tags( preg_replace('@<span[^>]*>.*</span>@i', '', $title) );
|
736 |
}
|
737 |
|
738 |
/**
|
739 |
+
* Merge a custom menu with the current default WordPress menu. Adds/replaces defaults,
|
740 |
+
* inserts new items and removes missing items.
|
741 |
*
|
742 |
+
* @uses self::$item_templates
|
743 |
+
*
|
744 |
+
* @param array $tree A menu in plugin's internal form
|
745 |
+
* @return array Updated menu tree
|
746 |
*/
|
747 |
+
function menu_merge($tree){
|
748 |
+
//Iterate over all menus and submenus and look up default values
|
749 |
+
foreach ($tree as &$topmenu){
|
750 |
+
|
751 |
+
if ( !ameMenuItem::get($topmenu, 'custom') ) {
|
752 |
+
$template_id = ameMenuItem::template_id($topmenu);
|
753 |
+
//Is this menu present in the default WP menu?
|
754 |
+
if (isset($this->item_templates[$template_id])){
|
755 |
+
//Yes, load defaults from that item
|
756 |
+
$topmenu['defaults'] = $this->item_templates[$template_id]['defaults'];
|
757 |
+
//Note that the original item was used
|
758 |
+
$this->item_templates[$template_id]['used'] = true;
|
759 |
+
} else {
|
760 |
+
//Record the menu as missing, unless it's a menu separator
|
761 |
+
if ( empty($topmenu['separator']) ){
|
762 |
+
$topmenu['missing'] = true;
|
763 |
+
|
764 |
+
$temp = ameMenuItem::apply_defaults($topmenu);
|
765 |
+
$temp = $this->set_final_menu_capability($temp);
|
766 |
+
$this->add_access_lookup($temp, 'menu', true);
|
767 |
+
}
|
768 |
+
}
|
769 |
+
}
|
770 |
+
|
771 |
+
if (is_array($topmenu['items'])) {
|
772 |
+
//Iterate over submenu items
|
773 |
+
foreach ($topmenu['items'] as &$item){
|
774 |
+
if ( !ameMenuItem::get($item, 'custom') ) {
|
775 |
+
$template_id = ameMenuItem::template_id($item);
|
776 |
+
|
777 |
+
//Is this item present in the default WP menu?
|
778 |
+
if (isset($this->item_templates[$template_id])){
|
779 |
+
//Yes, load defaults from that item
|
780 |
+
$item['defaults'] = $this->item_templates[$template_id]['defaults'];
|
781 |
+
$this->item_templates[$template_id]['used'] = true;
|
782 |
+
} else if ( empty($item['separator']) ) {
|
783 |
+
//Record as missing, unless it's a menu separator
|
784 |
+
$item['missing'] = true;
|
785 |
+
|
786 |
+
$temp = ameMenuItem::apply_defaults($item);
|
787 |
+
$temp = $this->set_final_menu_capability($temp);
|
788 |
+
$this->add_access_lookup($temp, 'submenu', true);
|
789 |
+
}
|
790 |
+
}
|
791 |
+
}
|
792 |
}
|
793 |
}
|
794 |
|
795 |
+
//If we don't unset these they will fuck up the next two loops where the same names are used.
|
796 |
+
unset($topmenu);
|
797 |
+
unset($item);
|
798 |
+
|
799 |
+
//Now we have some items marked as missing, and some items in lookup arrays
|
800 |
+
//that are not marked as used. Lets remove the missing items from the tree.
|
801 |
+
$filteredTree = array();
|
802 |
+
foreach($tree as $file => $topmenu) {
|
803 |
+
if ( $topmenu['missing'] ) {
|
804 |
+
continue;
|
805 |
+
}
|
806 |
+
$filteredSubmenu = array();
|
807 |
+
if (is_array($topmenu['items'])) {
|
808 |
+
foreach($topmenu['items'] as $index => $item) {
|
809 |
+
if ( !$item['missing'] ) {
|
810 |
+
$filteredSubmenu[$index] = $item;
|
811 |
+
}
|
812 |
+
}
|
813 |
+
|
814 |
+
}
|
815 |
+
$topmenu['items'] = $filteredSubmenu;
|
816 |
+
$filteredTree[$file] = $topmenu;
|
817 |
+
}
|
818 |
+
|
819 |
+
$tree = $filteredTree;
|
820 |
+
|
821 |
+
//Lets merge in the unused items.
|
822 |
+
foreach ($this->item_templates as $template_id => $template){
|
823 |
+
//Skip used menus and separators
|
824 |
+
if ( !empty($template['used']) || !empty($template['defaults']['separator'])) {
|
825 |
+
continue;
|
826 |
+
}
|
827 |
+
|
828 |
+
//Found an unused item. Build the tree entry.
|
829 |
+
$entry = ameMenuItem::blank_menu();
|
830 |
+
$entry['template_id'] = $template_id;
|
831 |
+
$entry['defaults'] = $template['defaults'];
|
832 |
+
$entry['unused'] = true; //Note that this item is unused
|
833 |
+
|
834 |
+
//Add the new entry to the menu tree
|
835 |
+
if ( !empty($template['defaults']['parent']) ) {
|
836 |
+
if (isset($tree[$template['defaults']['parent']])) {
|
837 |
+
//Okay, insert the item.
|
838 |
+
$tree[$template['defaults']['parent']]['items'][] = $entry;
|
839 |
+
} else {
|
840 |
+
//This can happen if the original parent menu has been moved to a submenu.
|
841 |
+
//Todo: Handle this unusual situation.
|
842 |
+
}
|
843 |
} else {
|
844 |
+
$tree[$template['defaults']['file']] = $entry;
|
845 |
}
|
846 |
}
|
847 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
848 |
//Resort the tree to ensure the found items are in the right spots
|
849 |
+
$tree = ameMenu::sort_menu_tree($tree);
|
850 |
+
|
851 |
+
return $tree;
|
852 |
+
}
|
853 |
+
|
854 |
+
/**
|
855 |
+
* Add a page and its required capability to the page access lookup.
|
856 |
+
*
|
857 |
+
* The lookup array is indexed by priority. Priorities (highest to lowest):
|
858 |
+
* - Has custom permissions and a known template.
|
859 |
+
* - Has custom permissions, template missing or can't be determined correctly.
|
860 |
+
* - Default permissions.
|
861 |
+
* - Everything else.
|
862 |
+
* Additionally, submenu items have slightly higher priority that top level menus.
|
863 |
+
* The desired end result is for menu items with custom permissions to override
|
864 |
+
* default menus.
|
865 |
+
*
|
866 |
+
* Note to self: If we were to keep items with an unknown template instead of throwing
|
867 |
+
* them away during the merge phase, we could simplify this considerably.
|
868 |
+
*
|
869 |
+
* @param array $item Menu item (with defaults already applied).
|
870 |
+
* @param string $item_type 'menu' or 'submenu'.
|
871 |
+
* @param bool $missing Whether the item template is missing or unknown.
|
872 |
+
*/
|
873 |
+
private function add_access_lookup($item, $item_type = 'menu', $missing = false) {
|
874 |
+
if ( empty($item['url']) ) {
|
875 |
+
return;
|
876 |
+
}
|
877 |
+
|
878 |
+
$has_custom_settings = !empty($item['grant_access']) || !empty($item['extra_capability']);
|
879 |
+
$priority = 6;
|
880 |
+
if ( $missing ) {
|
881 |
+
if ( $has_custom_settings ) {
|
882 |
+
$priority = 4;
|
883 |
+
} else {
|
884 |
+
return; //Don't even consider missing menus without custom access settings.
|
885 |
}
|
886 |
+
} else if ( $has_custom_settings ) {
|
887 |
+
$priority = 2;
|
888 |
}
|
889 |
+
|
890 |
+
if ( $item_type == 'submenu' ) {
|
891 |
+
$priority--;
|
892 |
+
}
|
893 |
+
|
894 |
+
$this->page_access_lookup[$item['url']][$priority] = $item['access_level'];
|
895 |
}
|
896 |
|
897 |
/**
|
898 |
+
* Generate WP-compatible $menu and $submenu arrays from a custom menu tree.
|
899 |
*
|
900 |
+
* Side-effects: This function executes several filters that may modify global state.
|
901 |
+
* Specifically, IFrame-handling callbacks in 'extras.php' will add add new hooks
|
902 |
+
* and other menu-related structures.
|
903 |
+
*
|
904 |
+
* @uses WPMenuEditor::$custom_wp_menu Stores the generated top-level menu here.
|
905 |
+
* @uses WPMenuEditor::$custom_wp_submenu Stores the generated sub-menu here.
|
906 |
*
|
907 |
+
* @uses WPMenuEditor::$title_lookups Generates a lookup list of page titles.
|
908 |
+
* @uses WPMenuEditor::$reverse_item_lookup Generates a lookup list of url => menu item relationships.
|
909 |
+
*
|
910 |
+
* @param array $tree The new menu, in the internal tree format.
|
911 |
+
* @return void
|
912 |
*/
|
913 |
+
function build_custom_wp_menu($tree){
|
914 |
+
$new_tree = array();
|
915 |
+
$new_menu = array();
|
916 |
+
$new_submenu = array();
|
917 |
+
$this->title_lookups = array();
|
918 |
|
919 |
//Sort the menu by position
|
920 |
+
uasort($tree, 'ameMenuItem::compare_position');
|
921 |
|
922 |
//Prepare the top menu
|
923 |
$first_nonseparator_found = false;
|
924 |
foreach ($tree as $topmenu){
|
925 |
+
|
926 |
+
//Skip missing and hidden menus.
|
927 |
+
if ( !empty($topmenu['missing']) || !empty($topmenu['hidden']) ) {
|
|
|
928 |
continue;
|
929 |
+
}
|
930 |
|
931 |
//Skip leading menu separators. Fixes a superfluous separator showing up
|
932 |
//in WP 3.0 (multisite mode) when there's a custom menu and the current user
|
933 |
//can't access its first item ("Super Admin").
|
934 |
+
if ( !empty($topmenu['separator']) && !$first_nonseparator_found ) {
|
935 |
+
continue;
|
936 |
+
}
|
937 |
$first_nonseparator_found = true;
|
938 |
|
939 |
+
$topmenu = $this->prepare_for_output($topmenu, 'menu');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
940 |
|
941 |
+
if ( empty($topmenu['separator']) ) {
|
942 |
+
$this->title_lookups[$topmenu['file']] = !empty($topmenu['page_title']) ? $topmenu['page_title'] : $topmenu['menu_title'];
|
943 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
944 |
|
945 |
//Prepare the submenu of this menu
|
946 |
+
$new_items = array();
|
947 |
if( !empty($topmenu['items']) ){
|
948 |
$items = $topmenu['items'];
|
949 |
//Sort by position
|
950 |
+
uasort($items, 'ameMenuItem::compare_position');
|
951 |
|
952 |
foreach ($items as $item) {
|
953 |
+
//Skip missing and hidden items
|
954 |
+
if ( !empty($item['missing']) || !empty($item['hidden']) ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
955 |
continue;
|
956 |
}
|
957 |
+
|
958 |
+
$item = $this->prepare_for_output($item, 'submenu', $topmenu['file']);
|
959 |
+
$new_items[] = $item;
|
960 |
+
|
961 |
+
//Make a note of the page's correct title so we can fix it later if necessary.
|
962 |
+
$this->title_lookups[$item['file']] = !empty($item['page_title']) ? $item['page_title'] : $item['menu_title'];
|
|
|
|
|
|
|
|
|
|
|
963 |
}
|
964 |
}
|
965 |
+
|
966 |
+
$topmenu['items'] = $new_items;
|
967 |
+
$new_tree[] = $topmenu;
|
968 |
+
}
|
969 |
+
|
970 |
+
//Use only the highest-priority capability for each URL.
|
971 |
+
foreach($this->page_access_lookup as $url => $capabilities) {
|
972 |
+
ksort($capabilities);
|
973 |
+
$this->page_access_lookup[$url] = reset($capabilities);
|
974 |
}
|
975 |
+
|
976 |
+
//Convert the prepared tree to the internal WordPress format.
|
977 |
+
foreach($new_tree as $topmenu) {
|
978 |
+
$trueAccess = isset($this->page_access_lookup[$topmenu['url']]) ? $this->page_access_lookup[$topmenu['url']] : null;
|
979 |
+
if ( $trueAccess === 'do_not_allow' ) {
|
980 |
+
$topmenu['access_level'] = $trueAccess;
|
981 |
+
}
|
982 |
+
if ( !isset($this->reverse_item_lookup[$topmenu['url']]) ) { //Prefer sub-menus.
|
983 |
+
$this->reverse_item_lookup[$topmenu['url']] = $topmenu;
|
984 |
+
}
|
985 |
+
|
986 |
+
$new_menu[] = $this->convert_to_wp_format($topmenu);
|
987 |
+
|
988 |
+
foreach($topmenu['items'] as $item) {
|
989 |
+
$trueAccess = isset($this->page_access_lookup[$item['url']]) ? $this->page_access_lookup[$item['url']] : null;
|
990 |
+
if ( $trueAccess === 'do_not_allow' ) {
|
991 |
+
$item['access_level'] = $trueAccess;
|
992 |
+
}
|
993 |
+
$this->reverse_item_lookup[$item['url']] = $item;
|
994 |
+
$new_submenu[$topmenu['file']][] = $this->convert_to_wp_format($item);
|
995 |
+
}
|
996 |
+
}
|
997 |
+
|
998 |
+
$this->custom_wp_menu = $new_menu;
|
999 |
+
$this->custom_wp_submenu = $new_submenu;
|
1000 |
}
|
1001 |
+
|
1002 |
+
/**
|
1003 |
+
* Convert a menu item from the internal format used by this plugin to the format
|
1004 |
+
* used by WP. The menu should be prepared using the prepare... function beforehand.
|
1005 |
+
*
|
1006 |
+
* @see self::prepare_for_output()
|
1007 |
+
*
|
1008 |
+
* @param array $item
|
1009 |
+
* @return array
|
1010 |
+
*/
|
1011 |
+
private function convert_to_wp_format($item) {
|
1012 |
+
//Build the menu structure that WP expects
|
1013 |
+
$wp_item = array(
|
1014 |
+
$item['menu_title'],
|
1015 |
+
$item['access_level'],
|
1016 |
+
$item['file'],
|
1017 |
+
$item['page_title'],
|
1018 |
+
$item['css_class'],
|
1019 |
+
$item['hookname'], //ID
|
1020 |
+
$item['icon_url']
|
1021 |
+
);
|
1022 |
+
|
1023 |
+
return $wp_item;
|
1024 |
+
}
|
1025 |
+
|
1026 |
+
/**
|
1027 |
+
* Prepare a menu item to be converted to the WordPress format and added to the current
|
1028 |
+
* WordPress admin menu. This function applies menu defaults and templates, calls filters
|
1029 |
+
* that allow other components to tweak the menu, decides on what capability/-ies to use,
|
1030 |
+
* and so on.
|
1031 |
+
*
|
1032 |
+
* Caution: The filters called by this function may cause side-effects. Specifically, the Pro-only feature
|
1033 |
+
* for displaying menu pages in a frame does this. See wsMenuEditorExtras::create_framed_menu().
|
1034 |
+
* Therefore, it is not safe to call this function more than once for the same item.
|
1035 |
+
*
|
1036 |
+
* @param array $item Menu item in the internal format.
|
1037 |
+
* @param string $item_type Either 'menu' or 'submenu'.
|
1038 |
+
* @param string $parent Optional. The parent of this sub-menu item. An empty string for top-level menus.
|
1039 |
+
* @return array Menu item in the internal format.
|
1040 |
+
*/
|
1041 |
+
private function prepare_for_output($item, $item_type = 'menu', $parent = '') {
|
1042 |
+
// Special case : plugin pages that have been moved from a sub-menu to a different
|
1043 |
+
// menu or the top level. We'll need to adjust the file field to point to the correct URL.
|
1044 |
+
// This is required because WP identifies plugin pages using *both* the plugin file
|
1045 |
+
// and the parent file.
|
1046 |
+
if ( $item['template_id'] !== '' && !$item['separator'] ) {
|
1047 |
+
$template = $this->item_templates[$item['template_id']];
|
1048 |
+
if ( $template['defaults']['is_plugin_page'] ) {
|
1049 |
+
$default_parent = $template['defaults']['parent'];
|
1050 |
+
if ( $parent != $default_parent ){
|
1051 |
+
$item['file'] = $template['defaults']['url'];
|
1052 |
+
}
|
1053 |
}
|
1054 |
}
|
1055 |
+
|
1056 |
+
//Menus that have both a custom icon URL and a "menu-icon-*" class will get two overlapping icons.
|
1057 |
+
//Fix this by automatically removing the class. The user can set a custom class attr. to override.
|
1058 |
+
if (
|
1059 |
+
ameMenuItem::is_default($item, 'css_class')
|
1060 |
+
&& !ameMenuItem::is_default($item, 'icon_url')
|
1061 |
+
&& !in_array($item['icon_url'], array('', 'none', 'div')) //Skip "no custom icon" icons.
|
1062 |
+
) {
|
1063 |
+
$new_classes = preg_replace('@\bmenu-icon-[^\s]+\b@', '', $item['defaults']['css_class']);
|
1064 |
+
if ( $new_classes !== $item['defaults']['css_class'] ) {
|
1065 |
+
$item['css_class'] = $new_classes;
|
1066 |
+
}
|
1067 |
+
}
|
1068 |
+
|
1069 |
+
//Apply defaults & filters
|
1070 |
+
$item = ameMenuItem::apply_defaults($item);
|
1071 |
+
$item = ameMenuItem::apply_filters($item, $item_type, $parent); //may cause side-effects
|
1072 |
+
|
1073 |
+
$item = $this->set_final_menu_capability($item);
|
1074 |
+
if ( !$this->options['security_logging_enabled'] ) {
|
1075 |
+
unset($item['access_check_log']); //Throw away the log to conserve memory.
|
1076 |
+
}
|
1077 |
+
$this->add_access_lookup($item, $item_type);
|
1078 |
+
|
1079 |
+
//Menus without a custom icon image should have it set to "none" (or "div" in older WP versions).
|
1080 |
+
//See /wp-admin/menu-header.php for details on how this works.
|
1081 |
+
if ( $item['icon_url'] === '' ) {
|
1082 |
+
$item['icon_url'] = 'none';
|
1083 |
+
}
|
1084 |
+
|
1085 |
+
//Used later to determine the current page based on URL.
|
1086 |
+
$item['url'] = ameMenuItem::generate_url($item['file'], $parent);
|
1087 |
+
|
1088 |
+
//Convert relative URls to fully qualified ones. This prevents problems with WordPress
|
1089 |
+
//incorrectly converting "index.php?page=xyz" to, say, "tools.php?page=index.php?page=xyz"
|
1090 |
+
//if the menu item was moved from "Dashboard" to "Tools".
|
1091 |
+
$itemFile = ameMenuItem::remove_query_from($item['file']);
|
1092 |
+
$shouldMakeAbsolute =
|
1093 |
+
(strpos($item['file'], '://') === false)
|
1094 |
+
&& (substr($item['file'], 0, 1) != '/')
|
1095 |
+
&& ($itemFile == 'index.php')
|
1096 |
+
&& (strpos($item['file'], '?') !== false);
|
1097 |
+
|
1098 |
+
if ( $shouldMakeAbsolute ) {
|
1099 |
+
$item['file'] = admin_url($item['url']);
|
1100 |
+
}
|
1101 |
+
|
1102 |
+
//WPML support: Use translated menu titles where available.
|
1103 |
+
if ( !$item['separator'] && function_exists('icl_t') ) {
|
1104 |
+
$item['menu_title'] = icl_t(
|
1105 |
+
self::WPML_CONTEXT,
|
1106 |
+
$this->get_wpml_name_for($item, 'menu_title'),
|
1107 |
+
$item['menu_title']
|
1108 |
+
);
|
1109 |
+
}
|
1110 |
+
|
1111 |
+
return $item;
|
1112 |
+
}
|
1113 |
+
|
1114 |
+
/**
|
1115 |
+
* Figure out if the current user can access a menu item and what capability they would need.
|
1116 |
+
*
|
1117 |
+
* This method takes into account the default capability set by WordPress as well as any
|
1118 |
+
* custom role and capability settings specified by the user. It will set "access_level"
|
1119 |
+
* to the required capability, or set it to 'do_not_allow' if the current user can't access
|
1120 |
+
* this menu.
|
1121 |
+
*
|
1122 |
+
* @param array $item Menu item (with defaults applied).
|
1123 |
+
* @return array
|
1124 |
+
*/
|
1125 |
+
private function set_final_menu_capability($item) {
|
1126 |
+
$item['access_check_log'] = array(
|
1127 |
+
str_repeat('=', 79),
|
1128 |
+
'Figuring out what capability the user will need to access this item...'
|
1129 |
+
);
|
1130 |
+
|
1131 |
+
$item = apply_filters('custom_admin_menu_capability', $item);
|
1132 |
+
|
1133 |
+
$item['access_check_log'][] = '-----';
|
1134 |
+
|
1135 |
+
//Check if the current user can access this menu.
|
1136 |
+
$user_has_access = true;
|
1137 |
+
$cap_to_use = '';
|
1138 |
+
if ( !empty($item['access_level']) ) {
|
1139 |
+
$user_has_cap = $this->current_user_can($item['access_level']);
|
1140 |
+
$user_has_access = $user_has_access && $user_has_cap;
|
1141 |
+
$cap_to_use = $item['access_level'];
|
1142 |
+
|
1143 |
+
$item['access_check_log'][] = sprintf(
|
1144 |
+
'Required capability: %1$s. User %2$s this capability.',
|
1145 |
+
htmlentities($cap_to_use),
|
1146 |
+
$user_has_cap ? 'HAS' : 'DOES NOT have'
|
1147 |
+
);
|
1148 |
+
} else {
|
1149 |
+
$item['access_check_log'][] = '- No required capability set.';
|
1150 |
+
}
|
1151 |
+
|
1152 |
+
if ( !empty($item['extra_capability']) ) {
|
1153 |
+
$user_has_cap = $this->current_user_can($item['extra_capability']);
|
1154 |
+
$user_has_access = $user_has_access && $user_has_cap;
|
1155 |
+
$cap_to_use = $item['extra_capability'];
|
1156 |
+
|
1157 |
+
$item['access_check_log'][] = sprintf(
|
1158 |
+
'Extra capability: %1$s. User %2$s this capability.',
|
1159 |
+
htmlentities($cap_to_use),
|
1160 |
+
$user_has_cap ? 'HAS' : 'DOES NOT have'
|
1161 |
+
);
|
1162 |
+
} else {
|
1163 |
+
$item['access_check_log'][] = 'No "extra capability" set.';
|
1164 |
+
}
|
1165 |
+
|
1166 |
+
$capability = $user_has_access ? $cap_to_use : 'do_not_allow';
|
1167 |
+
$item['access_check_log'][] = 'Final capability setting: ' . $capability;
|
1168 |
+
$item['access_check_log'][] = str_repeat('=', 79);
|
1169 |
+
|
1170 |
+
$item['access_level'] = $capability;
|
1171 |
+
return $item;
|
1172 |
}
|
1173 |
|
1174 |
/**
|
1177 |
* @return void
|
1178 |
*/
|
1179 |
function page_menu_editor(){
|
|
|
|
|
|
|
1180 |
if ( !$this->current_user_can_edit_menu() ){
|
1181 |
+
wp_die(sprintf(
|
1182 |
+
'You do not have sufficient permissions to use Admin Menu Editor. Required: <code>%s</code>.',
|
1183 |
+
htmlentities($this->options['plugin_access'])
|
1184 |
+
));
|
1185 |
}
|
1186 |
|
1187 |
$action = isset($this->post['action']) ? $this->post['action'] : (isset($this->get['action']) ? $this->get['action'] : '');
|
1188 |
do_action('admin_menu_editor_header', $action);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1189 |
|
1190 |
+
if ( !empty($action) ) {
|
1191 |
+
$this->handle_form_submission($this->post, $action);
|
1192 |
+
}
|
1193 |
+
|
1194 |
+
$sub_section = isset($this->get['sub_section']) ? $this->get['sub_section'] : null;
|
1195 |
+
if ( $sub_section === 'settings' ) {
|
1196 |
+
$this->display_plugin_settings_ui();
|
1197 |
+
} else {
|
1198 |
+
$this->display_editor_ui();
|
1199 |
+
}
|
1200 |
+
}
|
1201 |
+
|
1202 |
+
private function handle_form_submission($post, $action = '') {
|
1203 |
+
if ( $action == 'save_menu' ) {
|
1204 |
+
//Save the admin menu configuration.
|
1205 |
+
if ( isset($post['data']) ){
|
1206 |
+
check_admin_referer('menu-editor-form');
|
1207 |
+
|
1208 |
+
//Try to decode a menu tree encoded as JSON
|
1209 |
+
$url = remove_query_arg(array('noheader'));
|
1210 |
+
try {
|
1211 |
+
$menu = ameMenu::load_json($post['data'], true);
|
1212 |
+
} catch (InvalidMenuException $ex) {
|
1213 |
+
//Or redirect & display the error message
|
1214 |
+
wp_redirect( add_query_arg('message', 2, $url) );
|
1215 |
+
die();
|
1216 |
}
|
1217 |
|
1218 |
//Save the custom menu
|
1219 |
+
$this->set_custom_menu($menu);
|
1220 |
+
|
1221 |
+
//Redirect back to the editor and display the success message.
|
1222 |
+
//Also, automatically select the last selected actor (convenience feature).
|
1223 |
+
$query = array('message' => 1);
|
1224 |
+
if ( isset($post['selected_actor']) && !empty($post['selected_actor']) ) {
|
1225 |
+
$query['selected_actor'] = strval($post['selected_actor']);
|
1226 |
+
}
|
1227 |
+
wp_redirect( add_query_arg($query, $url) );
|
1228 |
+
die();
|
1229 |
} else {
|
1230 |
+
$message = "Failed to save the menu. ";
|
1231 |
+
if ( isset($this->post['data_length']) && is_numeric($this->post['data_length']) ) {
|
1232 |
+
$message .= sprintf(
|
1233 |
+
'Expected to receive %d bytes of menu data in $_POST[\'data\'], but got nothing.',
|
1234 |
+
intval($this->post['data_length'])
|
1235 |
+
);
|
1236 |
+
}
|
1237 |
+
wp_die($message);
|
1238 |
}
|
|
|
|
|
1239 |
|
1240 |
+
} else if ( $action == 'save_settings' ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1241 |
|
1242 |
+
//Save overall plugin configuration (permissions, etc).
|
1243 |
+
check_admin_referer('save_settings');
|
1244 |
+
|
1245 |
+
//Plugin access setting.
|
1246 |
+
$valid_access_settings = array('super_admin', 'manage_options');
|
1247 |
+
//On Multisite only Super Admins can choose the "Only the current user" option.
|
1248 |
+
if ( !is_multisite() || is_super_admin() ) {
|
1249 |
+
$valid_access_settings[] = 'specific_user';
|
1250 |
+
}
|
1251 |
+
if ( isset($this->post['plugin_access']) && in_array($this->post['plugin_access'], $valid_access_settings) ) {
|
1252 |
+
$this->options['plugin_access'] = $this->post['plugin_access'];
|
1253 |
+
|
1254 |
+
if ( $this->options['plugin_access'] === 'specific_user' ) {
|
1255 |
+
$this->options['allowed_user_id'] = get_current_user_id();
|
1256 |
+
} else {
|
1257 |
+
$this->options['allowed_user_id'] = null;
|
1258 |
+
}
|
1259 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1260 |
|
1261 |
+
//Whether to hide the plugin on the "Plugins" admin page.
|
1262 |
+
if ( !is_multisite() || is_super_admin() ) {
|
1263 |
+
if ( !empty($this->post['hide_plugin_from_others']) ) {
|
1264 |
+
$this->options['plugins_page_allowed_user_id'] = get_current_user_id();
|
1265 |
+
} else {
|
1266 |
+
$this->options['plugins_page_allowed_user_id'] = null;
|
1267 |
+
}
|
1268 |
+
}
|
1269 |
|
1270 |
+
//Configuration scope. The Super Admin is the only one who can change it since it affects all sites.
|
1271 |
+
if ( is_multisite() && is_super_admin() ) {
|
1272 |
+
$valid_scopes = array('global', 'site');
|
1273 |
+
if ( isset($this->post['menu_config_scope']) && in_array($this->post['menu_config_scope'], $valid_scopes) ) {
|
1274 |
+
$this->options['menu_config_scope'] = $this->post['menu_config_scope'];
|
1275 |
+
}
|
1276 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1277 |
|
1278 |
+
//Security logging.
|
1279 |
+
$this->options['security_logging_enabled'] = !empty($this->post['security_logging_enabled']);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1280 |
|
1281 |
+
//Hide some menu options by default.
|
1282 |
+
$this->options['hide_advanced_settings'] = !empty($this->post['hide_advanced_settings']);
|
1283 |
|
1284 |
+
$this->save_options();
|
1285 |
+
wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
|
1286 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1287 |
}
|
1288 |
|
1289 |
+
private function display_editor_ui() {
|
1290 |
+
//Prepare a bunch of parameters for the editor.
|
1291 |
+
$editor_data = array(
|
1292 |
+
'message' => isset($this->get['message']) ? intval($this->get['message']) : null,
|
1293 |
+
'images_url' => plugins_url('images', $this->plugin_file),
|
1294 |
+
'hide_advanced_settings' => $this->options['hide_advanced_settings'],
|
1295 |
+
'settings_page_url' => $this->get_settings_page_url(),
|
|
|
|
|
1296 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1297 |
|
1298 |
+
//Build a tree struct. for the default menu
|
1299 |
+
$default_tree = ameMenu::wp2tree($this->default_wp_menu, $this->default_wp_submenu);
|
1300 |
+
$default_menu = ameMenu::load_array($default_tree);
|
1301 |
+
|
1302 |
+
//Is there a custom menu?
|
1303 |
+
if (!empty($this->merged_custom_menu)){
|
1304 |
+
$custom_menu = $this->merged_custom_menu;
|
1305 |
+
} else {
|
1306 |
+
//Start out with the default menu if there is no user-created one
|
1307 |
+
$custom_menu = $default_menu;
|
1308 |
+
}
|
1309 |
+
|
1310 |
+
//Encode both menus as JSON
|
1311 |
+
$editor_data['default_menu_js'] = ameMenu::to_json($default_menu);
|
1312 |
+
$editor_data['custom_menu_js'] = ameMenu::to_json($custom_menu);
|
1313 |
+
|
1314 |
+
//Create a list of all known capabilities and roles. Used for the drop-down list on the access field.
|
1315 |
+
$all_capabilities = ameRoleUtils::get_all_capabilities();
|
1316 |
+
//"level_X" capabilities are deprecated so we don't want people using them.
|
1317 |
+
//This would look better with array_filter() and an anonymous function as a callback.
|
1318 |
+
for($level = 0; $level <= 10; $level++){
|
1319 |
+
$cap = 'level_' . $level;
|
1320 |
+
if ( isset($all_capabilities[$cap]) ){
|
1321 |
+
unset($all_capabilities[$cap]);
|
1322 |
+
}
|
1323 |
}
|
1324 |
+
$all_capabilities = array_keys($all_capabilities);
|
1325 |
+
natcasesort($all_capabilities);
|
|
|
1326 |
|
1327 |
+
//Multi-site installs also get the virtual "Super Admin" cap, but only the Super Admin sees it.
|
1328 |
+
if ( is_multisite() && !isset($all_capabilities['super_admin']) && is_super_admin() ){
|
1329 |
+
array_unshift($all_capabilities, 'super_admin');
|
1330 |
+
}
|
1331 |
+
$editor_data['all_capabilities'] = $all_capabilities;
|
1332 |
|
1333 |
+
//Create a list of all roles, too.
|
1334 |
+
$all_roles = ameRoleUtils::get_role_names();
|
1335 |
+
asort($all_roles);
|
1336 |
+
$editor_data['all_roles'] = $all_roles;
|
1337 |
|
1338 |
+
//Include hint visibility settings
|
1339 |
+
$editor_data['show_hints'] = $this->get_hint_visibility();
|
1340 |
|
1341 |
+
require dirname(__FILE__) . '/editor-page.php';
|
1342 |
+
}
|
1343 |
|
1344 |
+
/**
|
1345 |
+
* Display the plugin settings page.
|
1346 |
+
*/
|
1347 |
+
private function display_plugin_settings_ui() {
|
1348 |
+
//These variables are used by settings-page.php.
|
1349 |
+
$settings = $this->options;
|
1350 |
+
$settings_page_url = $this->get_settings_page_url();
|
1351 |
+
$editor_page_url = admin_url($this->settings_link);
|
1352 |
|
1353 |
+
require dirname(__FILE__) . '/settings-page.php';
|
1354 |
+
}
|
1355 |
|
1356 |
+
/**
|
1357 |
+
* Get the fully qualified URL of the "Settings" sub-section of our plugin page.
|
1358 |
+
*
|
1359 |
+
* @return string
|
1360 |
+
*/
|
1361 |
+
private function get_settings_page_url() {
|
1362 |
+
return add_query_arg('sub_section', 'settings', admin_url($this->settings_link));
|
1363 |
+
}
|
1364 |
|
1365 |
+
/**
|
1366 |
+
* Check if the current page is the "Menu Editor" admin page.
|
1367 |
+
*
|
1368 |
+
* @return bool
|
1369 |
+
*/
|
1370 |
+
protected function is_editor_page() {
|
1371 |
+
return is_admin()
|
1372 |
+
&& isset($this->get['page']) && ($this->get['page'] == 'menu_editor')
|
1373 |
+
&& ( !isset($this->get['sub_section']) || empty($this->get['sub_section']) );
|
1374 |
+
}
|
1375 |
|
1376 |
+
/**
|
1377 |
+
* Check if the current page is the "Settings" sub-section of our admin page.
|
1378 |
+
*
|
1379 |
+
* @return bool
|
1380 |
+
*/
|
1381 |
+
protected function is_settings_page() {
|
1382 |
+
return is_admin()
|
1383 |
+
&& isset($this->get['sub_section']) && ($this->get['sub_section'] == 'settings')
|
1384 |
+
&& isset($this->get['page']) && ($this->get['page'] == 'menu_editor');
|
1385 |
}
|
1386 |
|
1387 |
+
/**
|
1388 |
+
* Generate a list of "virtual" capabilities that should be granted to certain roles.
|
1389 |
+
*
|
1390 |
+
* This is based on grant_access settings for the current custom menu and enables
|
1391 |
+
* selected roles and users to access menu items that they ordinarily would not
|
1392 |
+
* be able to.
|
1393 |
+
*
|
1394 |
+
* @uses self::get_virtual_caps_for() to actually generate the caps.
|
1395 |
+
* @uses self::$cached_virtual_caps to cache the generated list of caps.
|
1396 |
+
*
|
1397 |
+
* @return array A list of capability => [role1 => true, ... roleN => true] assignments.
|
1398 |
+
*/
|
1399 |
+
function get_virtual_caps() {
|
1400 |
+
if ( $this->cached_virtual_caps !== null ) {
|
1401 |
+
return $this->cached_virtual_caps;
|
1402 |
}
|
1403 |
+
|
1404 |
+
$caps = array();
|
1405 |
+
$custom_menu = $this->load_custom_menu();
|
1406 |
+
if ( $custom_menu === null ){
|
1407 |
+
return $caps;
|
|
|
1408 |
}
|
1409 |
+
|
1410 |
+
foreach($custom_menu['tree'] as $item) {
|
1411 |
+
$caps = array_merge_recursive($caps, $this->get_virtual_caps_for($item));
|
1412 |
+
}
|
1413 |
+
|
1414 |
+
$this->cached_virtual_caps = $caps;
|
1415 |
+
return $caps;
|
|
|
|
|
|
|
|
|
|
|
|
|
1416 |
}
|
1417 |
+
|
1418 |
+
private function get_virtual_caps_for($item) {
|
1419 |
+
$caps = array();
|
1420 |
+
|
1421 |
+
if ( $item['template_id'] !== '' ) {
|
1422 |
+
$required_cap = ameMenuItem::get($item, 'access_level');
|
1423 |
+
foreach ($item['grant_access'] as $grant => $has_access) {
|
1424 |
+
if ( $has_access ) {
|
1425 |
+
if ( !isset($caps[$grant]) ) {
|
1426 |
+
$caps[$grant] = array();
|
1427 |
+
}
|
1428 |
+
$caps[$grant][$required_cap] = true;
|
1429 |
+
}
|
1430 |
+
}
|
1431 |
}
|
1432 |
+
|
1433 |
+
foreach($item['items'] as $sub_item) {
|
1434 |
+
$caps = array_merge_recursive($caps, $this->get_virtual_caps_for($sub_item));
|
1435 |
}
|
1436 |
+
|
1437 |
+
return $caps;
|
1438 |
}
|
1439 |
|
1440 |
/**
|
1455 |
return $allcaps;
|
1456 |
}
|
1457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1458 |
/**
|
1459 |
* AJAX callback for saving screen options (whether to show or to hide advanced menu options).
|
1460 |
*
|
1464 |
* @return void
|
1465 |
*/
|
1466 |
function ajax_save_screen_options(){
|
1467 |
+
if (!$this->current_user_can_edit_menu() || !check_ajax_referer('ws_ame_save_screen_options', false, false)){
|
1468 |
die( $this->json_encode( array(
|
1469 |
'error' => "You're not allowed to do that!"
|
1470 |
)));
|
1496 |
|
1497 |
$defaults = array(
|
1498 |
'ws_sidebar_pro_ad' => true,
|
1499 |
+
'ws_whats_new_120' => false,
|
1500 |
'ws_hint_menu_permissions' => true,
|
1501 |
);
|
1502 |
|
1513 |
* would not be highlighted properly when the user visits them.
|
1514 |
*/
|
1515 |
public function enqueue_menu_fix_script() {
|
1516 |
+
wp_enqueue_auto_versioned_script(
|
1517 |
'ame-menu-fix',
|
1518 |
plugins_url('js/menu-highlight-fix.js', $this->plugin_file),
|
1519 |
array('jquery'),
|
|
|
1520 |
true
|
1521 |
);
|
1522 |
}
|
1523 |
+
|
1524 |
/**
|
1525 |
+
* Check if the current user can access the current admin menu page.
|
1526 |
+
*
|
1527 |
+
* @return bool
|
1528 |
+
*/
|
1529 |
+
private function user_can_access_current_page() {
|
1530 |
+
$current_item = $this->get_current_menu_item();
|
1531 |
+
if ( $current_item === null ) {
|
1532 |
+
$this->log_security_note('Could not determine the current menu item. We won\'t do any custom permission checks.');
|
1533 |
+
return true; //Let WordPress handle it.
|
1534 |
+
}
|
1535 |
+
|
1536 |
+
$this->log_security_note(sprintf(
|
1537 |
+
'The current menu item is "%s", menu template ID: "%s"',
|
1538 |
+
htmlentities($current_item['menu_title']),
|
1539 |
+
htmlentities(ameMenuItem::get($current_item, 'template_id', 'N/A'))
|
1540 |
+
));
|
1541 |
+
if ( isset($current_item['access_check_log']) ) {
|
1542 |
+
$this->log_security_note($current_item['access_check_log']);
|
1543 |
+
}
|
1544 |
+
|
1545 |
+
//Note: Per-role and per-user virtual caps will be applied by has_cap filters.
|
1546 |
+
$allow = $this->current_user_can($current_item['access_level']);
|
1547 |
+
$this->log_security_note(sprintf(
|
1548 |
+
'The current user %1$s the "%2$s" capability.',
|
1549 |
+
$allow ? 'has' : 'does not have',
|
1550 |
+
htmlentities($current_item['access_level'])
|
1551 |
+
));
|
1552 |
+
|
1553 |
+
return $allow;
|
1554 |
+
}
|
1555 |
+
|
1556 |
+
/**
|
1557 |
+
* Check if the current user has the specified capability.
|
1558 |
+
* If the Pro version installed, you can use special syntax to perform complex capability checks.
|
1559 |
+
*
|
1560 |
+
* @param string $capability
|
1561 |
+
* @return bool
|
1562 |
+
*/
|
1563 |
+
private function current_user_can($capability) {
|
1564 |
+
return apply_filters('admin_menu_editor-current_user_can', current_user_can($capability), $capability);
|
1565 |
+
}
|
1566 |
+
|
1567 |
+
/**
|
1568 |
+
* Determine which menu item matches the currently open admin page.
|
1569 |
+
*
|
1570 |
+
* @uses self::$reverse_item_lookup
|
1571 |
+
* @return array|null Menu item in the internal format, or NULL if no matching item can be found.
|
1572 |
+
*/
|
1573 |
+
private function get_current_menu_item() {
|
1574 |
+
if ( !is_admin() || empty($this->reverse_item_lookup)) {
|
1575 |
+
if ( !is_admin() ) {
|
1576 |
+
$this->log_security_note('This is not an admin page. is_admin() returns false.');
|
1577 |
+
} else if ( empty($this->reverse_item_lookup) ) {
|
1578 |
+
$this->log_security_note('Warning: reverse_item_lookup is empty!');
|
1579 |
+
}
|
1580 |
+
return null;
|
1581 |
+
}
|
1582 |
+
|
1583 |
+
//The current menu item doesn't change during a request, so we can cache it
|
1584 |
+
//and avoid searching the entire menu every time.
|
1585 |
+
static $cached_item = null;
|
1586 |
+
if ( $cached_item !== null ) {
|
1587 |
+
return $cached_item;
|
1588 |
+
}
|
1589 |
+
|
1590 |
+
//Find an item where *all* query params match the current ones, with as few extraneous params as possible,
|
1591 |
+
//preferring sub-menu items. This is intentionally more strict than what we do in menu-highlight-fix.js,
|
1592 |
+
//since this function is used to check menu access.
|
1593 |
+
//TODO: Use get_current_screen() to determine the current post type and taxonomy.
|
1594 |
+
|
1595 |
+
$best_item = null;
|
1596 |
+
$best_extra_params = PHP_INT_MAX;
|
1597 |
+
|
1598 |
+
$base_site_url = get_site_url();
|
1599 |
+
if ( preg_match('@(^\w+://[^/]+)@', $base_site_url, $matches) ) { //Extract scheme + hostname.
|
1600 |
+
$base_site_url = $matches[1];
|
1601 |
+
}
|
1602 |
+
|
1603 |
+
$current_url = $base_site_url . remove_query_arg('___ame_dummy_param___');
|
1604 |
+
$this->log_security_note(sprintf('Current URL: "%s"', htmlentities($current_url)));
|
1605 |
+
|
1606 |
+
$current_url = $this->parse_url($current_url);
|
1607 |
+
|
1608 |
+
foreach($this->reverse_item_lookup as $url => $item) {
|
1609 |
+
$item_url = $url;
|
1610 |
+
//Convert to absolute URL. Caution: directory traversal (../, etc) is not handled.
|
1611 |
+
if (strpos($item_url, '://') === false) {
|
1612 |
+
if ( substr($item_url, 0, 1) == '/' ) {
|
1613 |
+
$item_url = $base_site_url . $item_url;
|
1614 |
+
} else {
|
1615 |
+
$item_url = admin_url($item_url);
|
1616 |
+
}
|
1617 |
+
}
|
1618 |
+
$item_url = $this->parse_url($item_url);
|
1619 |
+
|
1620 |
+
//Must match scheme, host, port, user, pass and path.
|
1621 |
+
$components = array('scheme', 'host', 'port', 'user', 'pass');
|
1622 |
+
$is_close_match = $this->urlPathsMatch($current_url['path'], $item_url['path']);
|
1623 |
+
foreach($components as $component) {
|
1624 |
+
$is_close_match = $is_close_match && ($current_url[$component] == $item_url[$component]);
|
1625 |
+
if ( !$is_close_match ) {
|
1626 |
+
break;
|
1627 |
+
}
|
1628 |
+
}
|
1629 |
+
|
1630 |
+
//The current URL must match all query parameters of the item URL.
|
1631 |
+
$different_params = array_diff_assoc($item_url['params'], $current_url['params']);
|
1632 |
+
|
1633 |
+
//The current URL must have as few extra parameters as possible.
|
1634 |
+
$extra_params = array_diff_assoc($current_url['params'], $item_url['params']);
|
1635 |
+
|
1636 |
+
if ( $is_close_match && (count($different_params) == 0) && (count($extra_params) < $best_extra_params) ) {
|
1637 |
+
$best_item = $item;
|
1638 |
+
$best_extra_params = count($extra_params);
|
1639 |
+
}
|
1640 |
+
}
|
1641 |
+
|
1642 |
+
$cached_item = $best_item;
|
1643 |
+
return $best_item;
|
1644 |
+
}
|
1645 |
+
|
1646 |
+
/**
|
1647 |
+
* Parse a URL and return its components.
|
1648 |
+
*
|
1649 |
+
* Returns an array that contains all of these components: 'scheme', 'host', 'port', 'user', 'pass',
|
1650 |
+
* 'path', 'query', 'fragment' and 'params'. All entries are strings, except 'params' which is
|
1651 |
+
* an associative array of query parameters and their values.
|
1652 |
+
*
|
1653 |
+
* @param string $url
|
1654 |
+
* @return array
|
1655 |
+
*/
|
1656 |
+
private function parse_url($url) {
|
1657 |
+
$url_defaults = array_fill_keys(array('scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment'), '');
|
1658 |
+
$url_defaults['port'] = '80';
|
1659 |
+
|
1660 |
+
$parsed = @parse_url($url);
|
1661 |
+
if ( !is_array($parsed) ) {
|
1662 |
+
$parsed = array();
|
1663 |
+
}
|
1664 |
+
$parsed = array_merge($url_defaults, $parsed);
|
1665 |
+
|
1666 |
+
$params = array();
|
1667 |
+
if (!empty($parsed['query'])) {
|
1668 |
+
wp_parse_str($parsed['query'], $params);
|
1669 |
+
};
|
1670 |
+
$parsed['params'] = $params;
|
1671 |
+
|
1672 |
+
return $parsed;
|
1673 |
+
}
|
1674 |
+
|
1675 |
+
/**
|
1676 |
+
* Check if two paths match. Intended for comparing WP admin URLs.
|
1677 |
+
*
|
1678 |
+
* @param string $path1
|
1679 |
+
* @param string $path2
|
1680 |
+
* @return bool
|
1681 |
+
*/
|
1682 |
+
private function urlPathsMatch($path1, $path2) {
|
1683 |
+
if ( $path1 == $path2 ) {
|
1684 |
+
return true;
|
1685 |
+
}
|
1686 |
+
|
1687 |
+
// "/wp-admin/index.php" should match "/wp-admin/".
|
1688 |
+
if (
|
1689 |
+
($this->endsWith($path1, '/wp-admin/index.php') && $this->endsWith($path2, '/wp-admin/'))
|
1690 |
+
|| ($this->endsWith($path2, '/wp-admin/index.php') && $this->endsWith($path1, '/wp-admin/'))
|
1691 |
+
) {
|
1692 |
+
return true;
|
1693 |
+
}
|
1694 |
+
|
1695 |
+
return false;
|
1696 |
+
}
|
1697 |
+
|
1698 |
+
/**
|
1699 |
+
* Determine if the input $string ends with the specified $suffix.
|
1700 |
+
*
|
1701 |
+
* @param string $string
|
1702 |
+
* @param string $suffix
|
1703 |
+
* @return bool
|
1704 |
*/
|
1705 |
+
private function endsWith($string, $suffix) {
|
1706 |
+
$len = strlen($suffix);
|
1707 |
+
if ( $len == 0 ) {
|
1708 |
+
return true;
|
1709 |
+
}
|
1710 |
+
return substr($string, -$len) === $suffix;
|
1711 |
+
}
|
1712 |
+
|
1713 |
+
private function castValuesToBool($capabilities) {
|
1714 |
+
if ( !is_array($capabilities) ) {
|
1715 |
+
if ( empty($capabilities) ) {
|
1716 |
+
$capabilities = array();
|
1717 |
+
} else {
|
1718 |
+
trigger_error("Unexpected capability array: " . print_r($capabilities, true), E_USER_WARNING);
|
1719 |
+
return array();
|
1720 |
+
}
|
1721 |
+
}
|
1722 |
+
foreach($capabilities as $capability => $value) {
|
1723 |
+
$capabilities[$capability] = (bool)$value;
|
1724 |
+
}
|
1725 |
+
return $capabilities;
|
1726 |
}
|
1727 |
|
1728 |
public function display_survey_notice() {
|
1739 |
$display_notice = $display_notice && ((time() - $this->options['first_install_time']) > $minimum_usage_period);
|
1740 |
}
|
1741 |
|
1742 |
+
//Only display the notice on the Menu Editor (Pro) page.
|
1743 |
$display_notice = $display_notice && isset($this->get['page']) && ($this->get['page'] == 'menu_editor');
|
1744 |
+
|
1745 |
+
//Let the user override this completely (useful for client sites).
|
1746 |
+
if ( $display_notice && file_exists(dirname($this->plugin_file) . '/never-display-surveys.txt') ) {
|
1747 |
+
$display_notice = false;
|
1748 |
+
$this->options['display_survey_notice'] = false;
|
1749 |
+
$this->save_options();
|
1750 |
+
}
|
1751 |
|
1752 |
if ( $display_notice ) {
|
1753 |
$free_survey_url = 'https://docs.google.com/spreadsheet/viewform?formkey=dERyeDk0OWhlbkxYcEY4QTNaMnlTQUE6MQ';
|
1763 |
printf(
|
1764 |
'<div class="updated">
|
1765 |
<p><strong>Help improve Admin Menu Editor - take the user survey!</strong></p>
|
1766 |
+
<p><!--suppress HtmlUnknownTarget --><a href="%s" target="_blank" title="Opens in a new window">Take the survey</a></p>
|
1767 |
+
<p><!--suppress HtmlUnknownTarget --><a href="%s">Hide this notice</a></p>
|
1768 |
</div>',
|
1769 |
esc_attr($survey_url),
|
1770 |
esc_attr($hide_url)
|
1788 |
}
|
1789 |
}
|
1790 |
|
1791 |
+
public function enqueue_helper_scripts() {
|
1792 |
+
wp_enqueue_script(
|
1793 |
+
'ame-helper-script',
|
1794 |
+
plugins_url('js/admin-helpers.js', $this->plugin_file),
|
1795 |
+
array('jquery'),
|
1796 |
+
'20121121'
|
1797 |
+
);
|
1798 |
+
}
|
1799 |
+
|
1800 |
+
public function enqueue_helper_styles() {
|
1801 |
+
wp_enqueue_style(
|
1802 |
+
'ame-helper-style',
|
1803 |
+
plugins_url('css/admin.css', $this->plugin_file),
|
1804 |
+
array(),
|
1805 |
+
'20130211'
|
1806 |
+
);
|
1807 |
+
}
|
1808 |
+
|
1809 |
+
/**
|
1810 |
+
* Get one of the plugin configuration values.
|
1811 |
+
*
|
1812 |
+
* @param string $name Option name.
|
1813 |
+
* @return mixed
|
1814 |
+
*/
|
1815 |
+
public function get_plugin_option($name) {
|
1816 |
+
if ( array_key_exists($name, $this->options) ) {
|
1817 |
+
return $this->options[$name];
|
1818 |
+
}
|
1819 |
+
return null;
|
1820 |
+
}
|
1821 |
+
|
1822 |
+
|
1823 |
+
/**
|
1824 |
+
* Log a security-related message.
|
1825 |
+
*
|
1826 |
+
* @param string|array $message The message to add tot he log, or an array of messages.
|
1827 |
+
*/
|
1828 |
+
private function log_security_note($message) {
|
1829 |
+
if ( !$this->options['security_logging_enabled'] ) {
|
1830 |
+
return;
|
1831 |
+
}
|
1832 |
+
if ( is_array($message) ) {
|
1833 |
+
$this->security_log = array_merge($this->security_log, $message);
|
1834 |
+
} else {
|
1835 |
+
$this->security_log[] = $message;
|
1836 |
+
}
|
1837 |
+
}
|
1838 |
+
|
1839 |
+
/**
|
1840 |
+
* Callback for "admin_notices".
|
1841 |
+
*/
|
1842 |
+
public function display_security_log() {
|
1843 |
+
?>
|
1844 |
+
<div class="updated">
|
1845 |
+
<h3>Admin Menu Editor security log</h3>
|
1846 |
+
<?php echo $this->get_formatted_security_log(); ?>
|
1847 |
+
</div>
|
1848 |
+
<?php
|
1849 |
+
}
|
1850 |
+
|
1851 |
+
/**
|
1852 |
+
* Get the security log in HTML format.
|
1853 |
+
*
|
1854 |
+
* @return string
|
1855 |
+
*/
|
1856 |
+
private function get_formatted_security_log() {
|
1857 |
+
$log = '<div style="font: 12px/16.8px Consolas, monospace; margin-bottom: 1em;">';
|
1858 |
+
$log .= implode("<br>\n", $this->security_log);
|
1859 |
+
$log .= '</div>';
|
1860 |
+
return $log;
|
1861 |
+
}
|
1862 |
+
|
1863 |
+
/**
|
1864 |
+
* WPML support: Update strings that need translation.
|
1865 |
+
*
|
1866 |
+
* @param array $old_menu The old custom menu, if any.
|
1867 |
+
* @param array $custom_menu The new custom menu.
|
1868 |
+
*/
|
1869 |
+
private function update_wpml_strings($old_menu, $custom_menu) {
|
1870 |
+
if ( !function_exists('icl_register_string') ) {
|
1871 |
+
return;
|
1872 |
+
}
|
1873 |
+
|
1874 |
+
$previous_strings = $this->get_wpml_strings($old_menu);
|
1875 |
+
$new_strings = $this->get_wpml_strings($custom_menu);
|
1876 |
+
|
1877 |
+
//Delete strings that are no longer valid.
|
1878 |
+
if ( function_exists('icl_unregister_string') ) {
|
1879 |
+
$removed_strings = array_diff_key($previous_strings, $new_strings);
|
1880 |
+
foreach($removed_strings as $name => $value) {
|
1881 |
+
icl_unregister_string(self::WPML_CONTEXT, $name);
|
1882 |
+
}
|
1883 |
+
}
|
1884 |
+
|
1885 |
+
//Register/update the new menu strings.
|
1886 |
+
foreach($new_strings as $name => $value) {
|
1887 |
+
icl_register_string(self::WPML_CONTEXT, $name, $value);
|
1888 |
+
}
|
1889 |
+
}
|
1890 |
+
|
1891 |
+
/**
|
1892 |
+
* Prepare WPML translation strings for all menu and page titles
|
1893 |
+
* in the specified menu.
|
1894 |
+
*
|
1895 |
+
* @param array $custom_menu
|
1896 |
+
* @return array Associative array of strings that can be translated, indexed by unique name.
|
1897 |
+
*/
|
1898 |
+
private function get_wpml_strings($custom_menu) {
|
1899 |
+
if ( empty($custom_menu) ) {
|
1900 |
+
return array();
|
1901 |
+
}
|
1902 |
+
|
1903 |
+
$strings = array();
|
1904 |
+
$translatable_fields = array('menu_title', 'page_title');
|
1905 |
+
foreach($custom_menu['tree'] as $top_menu) {
|
1906 |
+
if ( $top_menu['separator'] ) {
|
1907 |
+
continue;
|
1908 |
+
}
|
1909 |
+
|
1910 |
+
foreach($translatable_fields as $field) {
|
1911 |
+
if ( isset($top_menu[$field]) ) {
|
1912 |
+
$name = $this->get_wpml_name_for($top_menu, $field);
|
1913 |
+
$strings[$name] = ameMenuItem::get($top_menu, $field);
|
1914 |
+
}
|
1915 |
+
}
|
1916 |
+
|
1917 |
+
if ( isset($top_menu['items']) && !empty($top_menu['items']) ) {
|
1918 |
+
foreach($top_menu['items'] as $item) {
|
1919 |
+
if ( $item['separator'] ) {
|
1920 |
+
continue;
|
1921 |
+
}
|
1922 |
+
|
1923 |
+
foreach($translatable_fields as $field) {
|
1924 |
+
if ( isset($item[$field]) ) {
|
1925 |
+
$name = $this->get_wpml_name_for($item, $field);
|
1926 |
+
$strings[$name] = ameMenuItem::get($item, $field);
|
1927 |
+
}
|
1928 |
+
}
|
1929 |
+
}
|
1930 |
+
}
|
1931 |
+
}
|
1932 |
+
|
1933 |
+
return $strings;
|
1934 |
+
}
|
1935 |
|
1936 |
+
/**
|
1937 |
+
* Create a unique name for a specific field of a specific menu item.
|
1938 |
+
* Intended for use with the icl_register_string() function.
|
1939 |
+
*
|
1940 |
+
* @param array $item Admin menu item in the internal format.
|
1941 |
+
* @param string $field Field name.
|
1942 |
+
* @return string
|
1943 |
+
*/
|
1944 |
+
private function get_wpml_name_for($item, $field = '') {
|
1945 |
+
$name = ameMenuItem::get($item, 'template_id');
|
1946 |
+
if ( empty($name) ) {
|
1947 |
+
$name = 'custom: ' . ameMenuItem::get($item, 'file');
|
1948 |
+
}
|
1949 |
+
if ( !empty($field) ) {
|
1950 |
+
$name = $name . '[' . $field. ']';
|
1951 |
+
}
|
1952 |
+
return $name;
|
1953 |
+
}
|
1954 |
|
1955 |
+
} //class
|
includes/menu-item.php
CHANGED
@@ -1,408 +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 |
}
|
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 |
}
|
includes/menu.php
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
abstract class ameMenu {
|
3 |
+
const format_name = 'Admin Menu Editor menu';
|
4 |
+
const format_version = '5.0';
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Load an admin menu from a JSON string.
|
8 |
+
*
|
9 |
+
* @static
|
10 |
+
* @throws InvalidMenuException when the supplied input is not a valid menu.
|
11 |
+
*
|
12 |
+
* @param string $json A JSON-encoded menu structure.
|
13 |
+
* @param bool $assume_correct_format Skip the format header check and assume everything is fine. Defaults to false.
|
14 |
+
* @return array
|
15 |
+
*/
|
16 |
+
public static function load_json($json, $assume_correct_format = false) {
|
17 |
+
$arr = json_decode($json, true);
|
18 |
+
if ( !is_array($arr) ) {
|
19 |
+
throw new InvalidMenuException('The input is not a valid JSON-encoded admin menu.');
|
20 |
+
}
|
21 |
+
return self::load_array($arr, $assume_correct_format);
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Load an admin menu structure from an associative array.
|
26 |
+
*
|
27 |
+
* @static
|
28 |
+
* @throws InvalidMenuException when the supplied input is not a valid menu.
|
29 |
+
*
|
30 |
+
* @param array $arr
|
31 |
+
* @param bool $assume_correct_format
|
32 |
+
* @return array
|
33 |
+
*/
|
34 |
+
public static function load_array($arr, $assume_correct_format = false){
|
35 |
+
if ( !$assume_correct_format ) {
|
36 |
+
if ( isset($arr['format']) && ($arr['format']['name'] == self::format_name) ) {
|
37 |
+
if ( !version_compare($arr['format']['version'], self::format_version, '<=') ) {
|
38 |
+
throw new InvalidMenuException("Can't load a menu created by a newer version of the plugin.");
|
39 |
+
}
|
40 |
+
} else {
|
41 |
+
return self::load_menu_40($arr);
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
if ( !(isset($arr['tree']) && is_array($arr['tree'])) ) {
|
46 |
+
throw new InvalidMenuException("Failed to load a menu - the menu tree is missing.");
|
47 |
+
}
|
48 |
+
|
49 |
+
$menu = array('tree' => array());
|
50 |
+
$menu = self::add_format_header($menu);
|
51 |
+
|
52 |
+
foreach($arr['tree'] as $file => $item) {
|
53 |
+
$menu['tree'][$file] = ameMenuItem::normalize($item);
|
54 |
+
}
|
55 |
+
|
56 |
+
return $menu;
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* "Pre-load" an old menu structure.
|
61 |
+
*
|
62 |
+
* In older versions of the plugin, the entire menu consisted of
|
63 |
+
* just the menu tree and nothing else. This was internally known as
|
64 |
+
* menu format "4".
|
65 |
+
*
|
66 |
+
* To improve portability and forward-compatibility, newer versions
|
67 |
+
* use a simple dictionary-based container instead, with the menu tree
|
68 |
+
* being one of the possible entries.
|
69 |
+
*
|
70 |
+
* @static
|
71 |
+
* @param array $arr
|
72 |
+
* @return array
|
73 |
+
*/
|
74 |
+
private static function load_menu_40($arr) {
|
75 |
+
//This is *very* basic and might need to be improved.
|
76 |
+
$menu = array('tree' => $arr);
|
77 |
+
return self::load_array($menu, true);
|
78 |
+
}
|
79 |
+
|
80 |
+
private static function add_format_header($menu) {
|
81 |
+
$menu['format'] = array(
|
82 |
+
'name' => self::format_name,
|
83 |
+
'version' => self::format_version,
|
84 |
+
);
|
85 |
+
return $menu;
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Serialize an admin menu as JSON.
|
90 |
+
*
|
91 |
+
* @static
|
92 |
+
* @param array $menu
|
93 |
+
* @return string
|
94 |
+
*/
|
95 |
+
public static function to_json($menu) {
|
96 |
+
$menu = self::add_format_header($menu);
|
97 |
+
return json_encode($menu);
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Sort the menus and menu items of a given menu according to their positions
|
102 |
+
*
|
103 |
+
* @param array $tree A menu structure in the internal format (just the tree).
|
104 |
+
* @return array Sorted menu in the internal format
|
105 |
+
*/
|
106 |
+
public static function sort_menu_tree($tree){
|
107 |
+
//Resort the tree to ensure the found items are in the right spots
|
108 |
+
uasort($tree, 'ameMenuItem::compare_position');
|
109 |
+
//Resort all submenus as well
|
110 |
+
foreach ($tree as &$topmenu){
|
111 |
+
if (!empty($topmenu['items'])){
|
112 |
+
uasort($topmenu['items'], 'ameMenuItem::compare_position');
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
return $tree;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Convert the WP menu structure to the internal representation. All properties set as defaults.
|
121 |
+
*
|
122 |
+
* @param array $menu
|
123 |
+
* @param array $submenu
|
124 |
+
* @return array Menu in the internal tree format.
|
125 |
+
*/
|
126 |
+
public static function wp2tree($menu, $submenu){
|
127 |
+
$tree = array();
|
128 |
+
foreach ($menu as $pos => $item){
|
129 |
+
|
130 |
+
$tree_item = ameMenuItem::blank_menu();
|
131 |
+
$tree_item['defaults'] = ameMenuItem::fromWpItem($item, $pos);
|
132 |
+
$tree_item['separator'] = $tree_item['defaults']['separator'];
|
133 |
+
|
134 |
+
//Attach sub-menu items
|
135 |
+
$parent = $tree_item['defaults']['file'];
|
136 |
+
if ( isset($submenu[$parent]) ){
|
137 |
+
foreach($submenu[$parent] as $position => $subitem){
|
138 |
+
$tree_item['items'][$subitem[2]] = array_merge(
|
139 |
+
ameMenuItem::blank_menu(),
|
140 |
+
array('defaults' => ameMenuItem::fromWpItem($subitem, $position, $parent))
|
141 |
+
);
|
142 |
+
}
|
143 |
+
}
|
144 |
+
|
145 |
+
$tree[$parent] = $tree_item;
|
146 |
+
}
|
147 |
+
|
148 |
+
$tree = self::sort_menu_tree($tree);
|
149 |
+
|
150 |
+
return $tree;
|
151 |
+
}
|
152 |
+
}
|
153 |
+
|
154 |
+
|
155 |
+
class InvalidMenuException extends Exception {}
|
includes/role-utils.php
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
12 |
+
//Iterate over all known roles and collect their capabilities
|
13 |
+
foreach($wp_roles->roles as $role){
|
14 |
+
if ( !empty($role['capabilities']) && is_array($role['capabilities']) ){ //Being defensive here
|
15 |
+
$capabilities = array_merge($capabilities, $role['capabilities']);
|
16 |
+
}
|
17 |
+
}
|
18 |
+
|
19 |
+
//Add multisite-specific capabilities (not listed in any roles in WP 3.0)
|
20 |
+
$multisite_caps = array(
|
21 |
+
'manage_sites' => 1,
|
22 |
+
'manage_network' => 1,
|
23 |
+
'manage_network_users' => 1,
|
24 |
+
'manage_network_themes' => 1,
|
25 |
+
'manage_network_options' => 1,
|
26 |
+
'manage_network_plugins' => 1,
|
27 |
+
);
|
28 |
+
$capabilities = array_merge($capabilities, $multisite_caps);
|
29 |
+
|
30 |
+
return $capabilities;
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Retrieve a list of all known roles and their names.
|
35 |
+
*
|
36 |
+
* @return array Associative array with role IDs as keys and role display names as values
|
37 |
+
*/
|
38 |
+
public static function get_role_names(){
|
39 |
+
$wp_roles = self::get_roles();
|
40 |
+
$roles = array();
|
41 |
+
|
42 |
+
foreach($wp_roles->roles as $role_id => $role){
|
43 |
+
$roles[$role_id] = $role['name'];
|
44 |
+
}
|
45 |
+
|
46 |
+
return $roles;
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Get all defined WordPress roles.
|
51 |
+
*
|
52 |
+
* @global WP_Roles $wp_roles
|
53 |
+
* @return WP_Roles
|
54 |
+
*/
|
55 |
+
public static function get_roles() {
|
56 |
+
global $wp_roles;
|
57 |
+
if ( !isset($wp_roles) ) {
|
58 |
+
$wp_roles = new WP_Roles();
|
59 |
+
}
|
60 |
+
//TODO: Do something about Super Admin
|
61 |
+
return $wp_roles;
|
62 |
+
}
|
63 |
+
}
|
includes/settings-page.php
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* This is the HTML template for the plugin settings page.
|
4 |
+
*
|
5 |
+
* These variables are provided by the plugin:
|
6 |
+
* @var array $settings Plugin settings.
|
7 |
+
* @var string $editor_page_url A fully qualified URL of the admin menu editor page.
|
8 |
+
* @var string $settings_page_url
|
9 |
+
*/
|
10 |
+
|
11 |
+
$currentUser = wp_get_current_user();
|
12 |
+
$isMultisite = is_multisite();
|
13 |
+
$isSuperAdmin = is_super_admin();
|
14 |
+
$formActionUrl = add_query_arg('noheader', 1, $settings_page_url);
|
15 |
+
$isProVersion = apply_filters('admin_menu_editor_is_pro', false);
|
16 |
+
?>
|
17 |
+
|
18 |
+
<div class="wrap">
|
19 |
+
<?php screen_icon(); ?>
|
20 |
+
<h2>
|
21 |
+
<?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?> Settings
|
22 |
+
<a href="<?php echo esc_attr($editor_page_url); ?>" class="add-new-h2"
|
23 |
+
title="Back to Admin Menu Editor">Editor</a>
|
24 |
+
</h2>
|
25 |
+
|
26 |
+
<form method="post" action="<?php echo esc_attr($formActionUrl); ?>" id="ws_plugin_settings_form">
|
27 |
+
|
28 |
+
<table class="form-table">
|
29 |
+
<tbody>
|
30 |
+
<tr>
|
31 |
+
<th scope="row">
|
32 |
+
Who can access this plugin
|
33 |
+
</th>
|
34 |
+
<td>
|
35 |
+
<fieldset>
|
36 |
+
<p>
|
37 |
+
<label>
|
38 |
+
<input type="radio" name="plugin_access" value="super_admin"
|
39 |
+
<?php checked('super_admin', $settings['plugin_access']); ?>
|
40 |
+
<?php disabled( !$isSuperAdmin ); ?>>
|
41 |
+
Super Admin
|
42 |
+
|
43 |
+
<?php if ( !$isMultisite ) : ?>
|
44 |
+
<br><span class="description">
|
45 |
+
On a single site installation this is usually
|
46 |
+
the same as the Administrator role.
|
47 |
+
</span>
|
48 |
+
<?php endif; ?>
|
49 |
+
</label>
|
50 |
+
</p>
|
51 |
+
|
52 |
+
<p>
|
53 |
+
<label>
|
54 |
+
<input type="radio" name="plugin_access" value="manage_options"
|
55 |
+
<?php checked('manage_options', $settings['plugin_access']); ?>
|
56 |
+
<?php disabled( !current_user_can('manage_options') ); ?>>
|
57 |
+
Anyone with the "manage_options" capability
|
58 |
+
|
59 |
+
<br><span class="description">
|
60 |
+
By default only Administrators have this capability.
|
61 |
+
</span>
|
62 |
+
</label>
|
63 |
+
</p>
|
64 |
+
|
65 |
+
<p>
|
66 |
+
<label>
|
67 |
+
<input type="radio" name="plugin_access" value="specific_user"
|
68 |
+
<?php checked('specific_user', $settings['plugin_access']); ?>
|
69 |
+
<?php disabled( $isMultisite && !$isSuperAdmin ); ?>>
|
70 |
+
Only the current user
|
71 |
+
|
72 |
+
<br>
|
73 |
+
<span class="description">
|
74 |
+
Login: <?php echo $currentUser->user_login; ?>,
|
75 |
+
user ID: <?php echo get_current_user_id(); ?>
|
76 |
+
</span>
|
77 |
+
</label>
|
78 |
+
</p>
|
79 |
+
</fieldset>
|
80 |
+
|
81 |
+
<p>
|
82 |
+
<label>
|
83 |
+
<input type="checkbox" name="hide_plugin_from_others" value="1"
|
84 |
+
<?php checked( $settings['plugins_page_allowed_user_id'] !== null ); ?>
|
85 |
+
<?php disabled( !$isProVersion || ($isMultisite && !is_super_admin()) ); ?>
|
86 |
+
>
|
87 |
+
Hide the "Admin Menu Editor<?php if ( $isProVersion ) { echo ' Pro'; } ?>" entry on the "Plugins" page from other users
|
88 |
+
<?php if ( !$isProVersion ) {
|
89 |
+
echo '(Pro version only)';
|
90 |
+
} ?>
|
91 |
+
</label>
|
92 |
+
</p>
|
93 |
+
</td>
|
94 |
+
</tr>
|
95 |
+
|
96 |
+
<tr>
|
97 |
+
<th scope="row">
|
98 |
+
Multisite settings
|
99 |
+
</th>
|
100 |
+
<td>
|
101 |
+
<fieldset id="ame-menu-scope-settings">
|
102 |
+
<p>
|
103 |
+
<label>
|
104 |
+
<input type="radio" name="menu_config_scope" value="global"
|
105 |
+
id="ame-menu-config-scope-global"
|
106 |
+
<?php checked('global', $settings['menu_config_scope']); ?>
|
107 |
+
<?php disabled(!$isMultisite || !$isSuperAdmin); ?>>
|
108 |
+
Global —
|
109 |
+
Use the same admin menu settings for all network sites.
|
110 |
+
</label><br>
|
111 |
+
</p>
|
112 |
+
|
113 |
+
|
114 |
+
<label>
|
115 |
+
<input type="radio" name="menu_config_scope" value="site"
|
116 |
+
<?php checked('site', $settings['menu_config_scope']); ?>
|
117 |
+
<?php disabled(!$isMultisite || !$isSuperAdmin); ?>>
|
118 |
+
Per-site —
|
119 |
+
Use different admin menu settings for each site.
|
120 |
+
</label>
|
121 |
+
</fieldset>
|
122 |
+
</td>
|
123 |
+
</tr>
|
124 |
+
|
125 |
+
<tr>
|
126 |
+
<th scope="row">Interface</th>
|
127 |
+
<td>
|
128 |
+
<label>
|
129 |
+
<input type="checkbox" name="hide_advanced_settings"
|
130 |
+
<?php checked($this->options['hide_advanced_settings']); ?>>
|
131 |
+
Hide advanced menu options by default
|
132 |
+
</label>
|
133 |
+
</td>
|
134 |
+
</tr>
|
135 |
+
|
136 |
+
<tr>
|
137 |
+
<th scope="row">Debugging</th>
|
138 |
+
<td>
|
139 |
+
<label>
|
140 |
+
<input type="checkbox" name="security_logging_enabled"
|
141 |
+
<?php checked($this->options['security_logging_enabled']); ?>>
|
142 |
+
Show menu access checks performed by the plugin on every admin page
|
143 |
+
</label>
|
144 |
+
<br><span class="description">
|
145 |
+
This can help track down configuration problems and figure out why
|
146 |
+
your menu permissions don't work the way they should.
|
147 |
+
|
148 |
+
Note: It's not recommended to use this option on a live site as
|
149 |
+
it can reveal information about your menu configuration.
|
150 |
+
</span>
|
151 |
+
</td>
|
152 |
+
</tr>
|
153 |
+
</tbody>
|
154 |
+
</table>
|
155 |
+
|
156 |
+
<input type="hidden" name="action" value="save_settings">
|
157 |
+
<?php
|
158 |
+
wp_nonce_field('save_settings');
|
159 |
+
submit_button();
|
160 |
+
?>
|
161 |
+
</form>
|
162 |
+
|
163 |
+
</div>
|
includes/shadow_plugin_framework.php
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
|
3 |
/**
|
4 |
* @author W-Shadow
|
5 |
-
* @copyright 2008-
|
6 |
*/
|
7 |
|
8 |
//Load JSON functions for PHP < 5.2
|
@@ -141,7 +141,7 @@ class MenuEd_ShadowPluginFramework {
|
|
141 |
* ShadowPluginFramework::save_options()
|
142 |
* Saves the $options array to the database.
|
143 |
*
|
144 |
-
* @return
|
145 |
*/
|
146 |
function save_options(){
|
147 |
if ($this->option_name) {
|
@@ -151,20 +151,21 @@ class MenuEd_ShadowPluginFramework {
|
|
151 |
}
|
152 |
|
153 |
if ( $this->sitewide_options ) {
|
154 |
-
update_site_option($this->option_name, $stored_options);
|
155 |
} else {
|
156 |
-
update_option($this->option_name, $stored_options);
|
157 |
}
|
158 |
}
|
|
|
159 |
}
|
160 |
|
161 |
|
162 |
/**
|
163 |
-
* Backwards
|
164 |
*
|
165 |
* @param string $data
|
166 |
* @param bool $assoc Decode objects as associative arrays.
|
167 |
-
* @return
|
168 |
*/
|
169 |
function json_decode($data, $assoc=false){
|
170 |
if ( function_exists('json_decode') ){
|
@@ -179,11 +180,12 @@ class MenuEd_ShadowPluginFramework {
|
|
179 |
return $json->decode($data);
|
180 |
} else {
|
181 |
trigger_error('No JSON parser available', E_USER_ERROR);
|
|
|
182 |
}
|
183 |
}
|
184 |
|
185 |
/**
|
186 |
-
* Backwards
|
187 |
*
|
188 |
* @param mixed $data
|
189 |
* @return string
|
@@ -200,6 +202,7 @@ class MenuEd_ShadowPluginFramework {
|
|
200 |
return $json->encode($data);
|
201 |
} else {
|
202 |
trigger_error('No JSON parser available', E_USER_ERROR);
|
|
|
203 |
}
|
204 |
}
|
205 |
|
@@ -214,7 +217,7 @@ class MenuEd_ShadowPluginFramework {
|
|
214 |
$class = new ReflectionClass(get_class($this));
|
215 |
$methods = $class->getMethods();
|
216 |
|
217 |
-
foreach ($methods as $method){
|
218 |
//Check if the method name starts with "hook_"
|
219 |
if (strpos($method->name, 'hook_') === 0){
|
220 |
//Get the hook's tag from the method name
|
2 |
|
3 |
/**
|
4 |
* @author W-Shadow
|
5 |
+
* @copyright 2008-2012
|
6 |
*/
|
7 |
|
8 |
//Load JSON functions for PHP < 5.2
|
141 |
* ShadowPluginFramework::save_options()
|
142 |
* Saves the $options array to the database.
|
143 |
*
|
144 |
+
* @return bool
|
145 |
*/
|
146 |
function save_options(){
|
147 |
if ($this->option_name) {
|
151 |
}
|
152 |
|
153 |
if ( $this->sitewide_options ) {
|
154 |
+
return update_site_option($this->option_name, $stored_options);
|
155 |
} else {
|
156 |
+
return update_option($this->option_name, $stored_options);
|
157 |
}
|
158 |
}
|
159 |
+
return false;
|
160 |
}
|
161 |
|
162 |
|
163 |
/**
|
164 |
+
* Backwards compatible json_decode.
|
165 |
*
|
166 |
* @param string $data
|
167 |
* @param bool $assoc Decode objects as associative arrays.
|
168 |
+
* @return mixed
|
169 |
*/
|
170 |
function json_decode($data, $assoc=false){
|
171 |
if ( function_exists('json_decode') ){
|
180 |
return $json->decode($data);
|
181 |
} else {
|
182 |
trigger_error('No JSON parser available', E_USER_ERROR);
|
183 |
+
return null;
|
184 |
}
|
185 |
}
|
186 |
|
187 |
/**
|
188 |
+
* Backwards compatible json_encode.
|
189 |
*
|
190 |
* @param mixed $data
|
191 |
* @return string
|
202 |
return $json->encode($data);
|
203 |
} else {
|
204 |
trigger_error('No JSON parser available', E_USER_ERROR);
|
205 |
+
return '';
|
206 |
}
|
207 |
}
|
208 |
|
217 |
$class = new ReflectionClass(get_class($this));
|
218 |
$methods = $class->getMethods();
|
219 |
|
220 |
+
foreach ($methods as $method){ /** @var ReflectionMethod $method */
|
221 |
//Check if the method name starts with "hook_"
|
222 |
if (strpos($method->name, 'hook_') === 0){
|
223 |
//Get the hook's tag from the method name
|
includes/version-conflict-check.php
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
//It's not possible to have two versions of this plugin active at the same time. Abort plugin load
|
3 |
+
//and display an error if we detect that another version has already been loaded.
|
4 |
+
if ( class_exists('WPMenuEditor') ) {
|
5 |
+
|
6 |
+
function ws_ame_activation_conflict() {
|
7 |
+
if ( !current_user_can('activate_plugins') ) {
|
8 |
+
return; //The current user can't do anything about the problem.
|
9 |
+
}
|
10 |
+
?>
|
11 |
+
<div class="error fade">
|
12 |
+
<p>
|
13 |
+
<strong>Error: Another version of Admin Menu Editor is already active.</strong><br>
|
14 |
+
Please deactivate the older version. It is not possible to run two different versions
|
15 |
+
of this plugin at the same time.
|
16 |
+
</p>
|
17 |
+
</div>
|
18 |
+
<?php
|
19 |
+
}
|
20 |
+
|
21 |
+
add_action('admin_notices', 'ws_ame_activation_conflict');
|
22 |
+
return true; //Conflict detected.
|
23 |
+
}
|
24 |
+
|
25 |
+
return false; //No conflict.
|
js/admin-helpers.js
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery(function($) {
|
2 |
+
//Menu separators shouldn't be clickable and should have a custom class.
|
3 |
+
$('#adminmenu')
|
4 |
+
.find('.ws-submenu-separator')
|
5 |
+
.closest('a').click(function() {
|
6 |
+
return false;
|
7 |
+
})
|
8 |
+
.closest('li').addClass('ws-submenu-separator-wrap');
|
9 |
+
});
|
js/jquery.form.js
CHANGED
@@ -1,372 +1,713 @@
|
|
1 |
/*!
|
2 |
* jQuery Form Plugin
|
3 |
-
* version:
|
4 |
-
* @requires jQuery v1.
|
5 |
*
|
6 |
* Examples and documentation at: http://malsup.com/jquery/form/
|
|
|
7 |
* Dual licensed under the MIT and GPL licenses:
|
8 |
-
*
|
9 |
-
*
|
10 |
*/
|
|
|
11 |
;(function($) {
|
|
|
12 |
|
13 |
/*
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
*/
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
/**
|
43 |
* ajaxSubmit() provides a mechanism for immediately submitting
|
44 |
* an HTML form using AJAX.
|
45 |
*/
|
46 |
$.fn.ajaxSubmit = function(options) {
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
};
|
371 |
|
372 |
/**
|
@@ -375,9 +716,9 @@ $.fn.ajaxSubmit = function(options) {
|
|
375 |
* The advantages of using this method instead of ajaxSubmit() are:
|
376 |
*
|
377 |
* 1: This method will include coordinates for <input type="image" /> elements (if the element
|
378 |
-
*
|
379 |
* 2. This method will include the submit element's name/value data (for the element that was
|
380 |
-
*
|
381 |
* 3. This method binds the submit() method to the form for you.
|
382 |
*
|
383 |
* The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
|
@@ -385,42 +726,83 @@ $.fn.ajaxSubmit = function(options) {
|
|
385 |
* the form itself.
|
386 |
*/
|
387 |
$.fn.ajaxForm = function(options) {
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
});
|
419 |
};
|
420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
421 |
// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
|
422 |
$.fn.ajaxFormUnbind = function() {
|
423 |
-
|
424 |
};
|
425 |
|
426 |
/**
|
@@ -434,45 +816,74 @@ $.fn.ajaxFormUnbind = function() {
|
|
434 |
* It is this array that is passed to pre-submit callback functions provided to the
|
435 |
* ajaxSubmit() and ajaxForm() methods.
|
436 |
*/
|
437 |
-
$.fn.formToArray = function(semantic) {
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
476 |
};
|
477 |
|
478 |
/**
|
@@ -480,8 +891,8 @@ $.fn.formToArray = function(semantic) {
|
|
480 |
* in the format: name1=value1&name2=value2
|
481 |
*/
|
482 |
$.fn.formSerialize = function(semantic) {
|
483 |
-
|
484 |
-
|
485 |
};
|
486 |
|
487 |
/**
|
@@ -489,47 +900,51 @@ $.fn.formSerialize = function(semantic) {
|
|
489 |
* This method will return a string in the format: name1=value1&name2=value2
|
490 |
*/
|
491 |
$.fn.fieldSerialize = function(successful) {
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
|
|
|
|
|
|
|
|
506 |
};
|
507 |
|
508 |
/**
|
509 |
* Returns the value(s) of the element in the matched set. For example, consider the following form:
|
510 |
*
|
511 |
* <form><fieldset>
|
512 |
-
*
|
513 |
-
*
|
514 |
-
*
|
515 |
-
*
|
516 |
-
*
|
517 |
-
*
|
518 |
* </fieldset></form>
|
519 |
*
|
520 |
-
* var v = $('
|
521 |
* // if no values are entered into the text inputs
|
522 |
* v == ['','']
|
523 |
* // if values entered into the text inputs are 'foo' and 'bar'
|
524 |
* v == ['foo','bar']
|
525 |
*
|
526 |
-
* var v = $('
|
527 |
* // if neither checkbox is checked
|
528 |
* v === undefined
|
529 |
* // if both checkboxes are checked
|
530 |
* v == ['B1', 'B2']
|
531 |
*
|
532 |
-
* var v = $('
|
533 |
* // if neither radio is checked
|
534 |
* v === undefined
|
535 |
* // if first radio is checked
|
@@ -541,51 +956,63 @@ $.fn.fieldSerialize = function(successful) {
|
|
541 |
* for each element is returned.
|
542 |
*
|
543 |
* Note: This method *always* returns an array. If no valid value can be determined the
|
544 |
-
*
|
545 |
*/
|
546 |
$.fn.fieldValue = function(successful) {
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
|
|
|
|
|
|
|
|
555 |
};
|
556 |
|
557 |
/**
|
558 |
* Returns the value of the field element.
|
559 |
*/
|
560 |
$.fieldValue = function(el, successful) {
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
589 |
};
|
590 |
|
591 |
/**
|
@@ -596,47 +1023,70 @@ $.fieldValue = function(el, successful) {
|
|
596 |
* - inputs of type submit, button, reset, and hidden will *not* be effected
|
597 |
* - button elements will *not* be effected
|
598 |
*/
|
599 |
-
$.fn.clearForm = function() {
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
};
|
604 |
|
605 |
/**
|
606 |
* Clears the selected form elements.
|
607 |
*/
|
608 |
-
$.fn.clearFields = $.fn.clearInputs = function() {
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
618 |
};
|
619 |
|
620 |
/**
|
621 |
* Resets the form data. Causes all form elements to be reset to their original value.
|
622 |
*/
|
623 |
$.fn.resetForm = function() {
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
|
|
630 |
};
|
631 |
|
632 |
/**
|
633 |
* Enables or disables any matching elements.
|
634 |
*/
|
635 |
$.fn.enable = function(b) {
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
|
|
|
|
640 |
};
|
641 |
|
642 |
/**
|
@@ -644,32 +1094,39 @@ $.fn.enable = function(b) {
|
|
644 |
* selects/deselects and matching option elements.
|
645 |
*/
|
646 |
$.fn.selected = function(select) {
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
|
|
|
|
|
|
661 |
};
|
662 |
|
|
|
|
|
|
|
663 |
// helper fn for console logging
|
664 |
-
// set $.fn.ajaxSubmit.debug to true to enable debug logging
|
665 |
function log() {
|
666 |
-
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
|
|
|
|
674 |
|
675 |
})(jQuery);
|
1 |
/*!
|
2 |
* jQuery Form Plugin
|
3 |
+
* version: 3.24 (26-DEC-2012)
|
4 |
+
* @requires jQuery v1.5 or later
|
5 |
*
|
6 |
* Examples and documentation at: http://malsup.com/jquery/form/
|
7 |
+
* Project repository: https://github.com/malsup/form
|
8 |
* Dual licensed under the MIT and GPL licenses:
|
9 |
+
* http://malsup.github.com/mit-license.txt
|
10 |
+
* http://malsup.github.com/gpl-license-v2.txt
|
11 |
*/
|
12 |
+
/*global ActiveXObject alert */
|
13 |
;(function($) {
|
14 |
+
"use strict";
|
15 |
|
16 |
/*
|
17 |
+
Usage Note:
|
18 |
+
-----------
|
19 |
+
Do not use both ajaxSubmit and ajaxForm on the same form. These
|
20 |
+
functions are mutually exclusive. Use ajaxSubmit if you want
|
21 |
+
to bind your own submit handler to the form. For example,
|
22 |
+
|
23 |
+
$(document).ready(function() {
|
24 |
+
$('#myForm').on('submit', function(e) {
|
25 |
+
e.preventDefault(); // <-- important
|
26 |
+
$(this).ajaxSubmit({
|
27 |
+
target: '#output'
|
28 |
+
});
|
29 |
+
});
|
30 |
+
});
|
31 |
+
|
32 |
+
Use ajaxForm when you want the plugin to manage all the event binding
|
33 |
+
for you. For example,
|
34 |
+
|
35 |
+
$(document).ready(function() {
|
36 |
+
$('#myForm').ajaxForm({
|
37 |
+
target: '#output'
|
38 |
+
});
|
39 |
+
});
|
40 |
+
|
41 |
+
You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
|
42 |
+
form does not have to exist when you invoke ajaxForm:
|
43 |
+
|
44 |
+
$('#myForm').ajaxForm({
|
45 |
+
delegation: true,
|
46 |
+
target: '#output'
|
47 |
+
});
|
48 |
+
|
49 |
+
When using ajaxForm, the ajaxSubmit function will be invoked for you
|
50 |
+
at the appropriate time.
|
51 |
*/
|
52 |
|
53 |
+
/**
|
54 |
+
* Feature detection
|
55 |
+
*/
|
56 |
+
var feature = {};
|
57 |
+
feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
|
58 |
+
feature.formdata = window.FormData !== undefined;
|
59 |
+
|
60 |
/**
|
61 |
* ajaxSubmit() provides a mechanism for immediately submitting
|
62 |
* an HTML form using AJAX.
|
63 |
*/
|
64 |
$.fn.ajaxSubmit = function(options) {
|
65 |
+
/*jshint scripturl:true */
|
66 |
+
|
67 |
+
// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
|
68 |
+
if (!this.length) {
|
69 |
+
log('ajaxSubmit: skipping submit process - no element selected');
|
70 |
+
return this;
|
71 |
+
}
|
72 |
+
|
73 |
+
var method, action, url, $form = this;
|
74 |
+
|
75 |
+
if (typeof options == 'function') {
|
76 |
+
options = { success: options };
|
77 |
+
}
|
78 |
+
|
79 |
+
method = this.attr('method');
|
80 |
+
action = this.attr('action');
|
81 |
+
url = (typeof action === 'string') ? $.trim(action) : '';
|
82 |
+
url = url || window.location.href || '';
|
83 |
+
if (url) {
|
84 |
+
// clean url (don't include hash vaue)
|
85 |
+
url = (url.match(/^([^#]+)/)||[])[1];
|
86 |
+
}
|
87 |
+
|
88 |
+
options = $.extend(true, {
|
89 |
+
url: url,
|
90 |
+
success: $.ajaxSettings.success,
|
91 |
+
type: method || 'GET',
|
92 |
+
iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
|
93 |
+
}, options);
|
94 |
+
|
95 |
+
// hook for manipulating the form data before it is extracted;
|
96 |
+
// convenient for use with rich editors like tinyMCE or FCKEditor
|
97 |
+
var veto = {};
|
98 |
+
this.trigger('form-pre-serialize', [this, options, veto]);
|
99 |
+
if (veto.veto) {
|
100 |
+
log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
|
101 |
+
return this;
|
102 |
+
}
|
103 |
+
|
104 |
+
// provide opportunity to alter form data before it is serialized
|
105 |
+
if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
|
106 |
+
log('ajaxSubmit: submit aborted via beforeSerialize callback');
|
107 |
+
return this;
|
108 |
+
}
|
109 |
+
|
110 |
+
var traditional = options.traditional;
|
111 |
+
if ( traditional === undefined ) {
|
112 |
+
traditional = $.ajaxSettings.traditional;
|
113 |
+
}
|
114 |
+
|
115 |
+
var elements = [];
|
116 |
+
var qx, a = this.formToArray(options.semantic, elements);
|
117 |
+
if (options.data) {
|
118 |
+
options.extraData = options.data;
|
119 |
+
qx = $.param(options.data, traditional);
|
120 |
+
}
|
121 |
+
|
122 |
+
// give pre-submit callback an opportunity to abort the submit
|
123 |
+
if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
|
124 |
+
log('ajaxSubmit: submit aborted via beforeSubmit callback');
|
125 |
+
return this;
|
126 |
+
}
|
127 |
+
|
128 |
+
// fire vetoable 'validate' event
|
129 |
+
this.trigger('form-submit-validate', [a, this, options, veto]);
|
130 |
+
if (veto.veto) {
|
131 |
+
log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
|
132 |
+
return this;
|
133 |
+
}
|
134 |
+
|
135 |
+
var q = $.param(a, traditional);
|
136 |
+
if (qx) {
|
137 |
+
q = ( q ? (q + '&' + qx) : qx );
|
138 |
+
}
|
139 |
+
if (options.type.toUpperCase() == 'GET') {
|
140 |
+
options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
|
141 |
+
options.data = null; // data is null for 'get'
|
142 |
+
}
|
143 |
+
else {
|
144 |
+
options.data = q; // data is the query string for 'post'
|
145 |
+
}
|
146 |
+
|
147 |
+
var callbacks = [];
|
148 |
+
if (options.resetForm) {
|
149 |
+
callbacks.push(function() { $form.resetForm(); });
|
150 |
+
}
|
151 |
+
if (options.clearForm) {
|
152 |
+
callbacks.push(function() { $form.clearForm(options.includeHidden); });
|
153 |
+
}
|
154 |
+
|
155 |
+
// perform a load on the target only if dataType is not provided
|
156 |
+
if (!options.dataType && options.target) {
|
157 |
+
var oldSuccess = options.success || function(){};
|
158 |
+
callbacks.push(function(data) {
|
159 |
+
var fn = options.replaceTarget ? 'replaceWith' : 'html';
|
160 |
+
$(options.target)[fn](data).each(oldSuccess, arguments);
|
161 |
+
});
|
162 |
+
}
|
163 |
+
else if (options.success) {
|
164 |
+
callbacks.push(options.success);
|
165 |
+
}
|
166 |
+
|
167 |
+
options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
|
168 |
+
var context = options.context || this ; // jQuery 1.4+ supports scope context
|
169 |
+
for (var i=0, max=callbacks.length; i < max; i++) {
|
170 |
+
callbacks[i].apply(context, [data, status, xhr || $form, $form]);
|
171 |
+
}
|
172 |
+
};
|
173 |
+
|
174 |
+
// are there files to upload?
|
175 |
+
|
176 |
+
// [value] (issue #113), also see comment:
|
177 |
+
// https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
|
178 |
+
var fileInputs = $('input[type=file]:enabled[value!=""]', this);
|
179 |
+
|
180 |
+
var hasFileInputs = fileInputs.length > 0;
|
181 |
+
var mp = 'multipart/form-data';
|
182 |
+
var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
|
183 |
+
|
184 |
+
var fileAPI = feature.fileapi && feature.formdata;
|
185 |
+
log("fileAPI :" + fileAPI);
|
186 |
+
var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
|
187 |
+
|
188 |
+
var jqxhr;
|
189 |
+
|
190 |
+
// options.iframe allows user to force iframe mode
|
191 |
+
// 06-NOV-09: now defaulting to iframe mode if file input is detected
|
192 |
+
if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
|
193 |
+
// hack to fix Safari hang (thanks to Tim Molendijk for this)
|
194 |
+
// see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
|
195 |
+
if (options.closeKeepAlive) {
|
196 |
+
$.get(options.closeKeepAlive, function() {
|
197 |
+
jqxhr = fileUploadIframe(a);
|
198 |
+
});
|
199 |
+
}
|
200 |
+
else {
|
201 |
+
jqxhr = fileUploadIframe(a);
|
202 |
+
}
|
203 |
+
}
|
204 |
+
else if ((hasFileInputs || multipart) && fileAPI) {
|
205 |
+
jqxhr = fileUploadXhr(a);
|
206 |
+
}
|
207 |
+
else {
|
208 |
+
jqxhr = $.ajax(options);
|
209 |
+
}
|
210 |
+
|
211 |
+
$form.removeData('jqxhr').data('jqxhr', jqxhr);
|
212 |
+
|
213 |
+
// clear element array
|
214 |
+
for (var k=0; k < elements.length; k++)
|
215 |
+
elements[k] = null;
|
216 |
+
|
217 |
+
// fire 'notify' event
|
218 |
+
this.trigger('form-submit-notify', [this, options]);
|
219 |
+
return this;
|
220 |
+
|
221 |
+
// utility fn for deep serialization
|
222 |
+
function deepSerialize(extraData){
|
223 |
+
var serialized = $.param(extraData).split('&');
|
224 |
+
var len = serialized.length;
|
225 |
+
var result = {};
|
226 |
+
var i, part;
|
227 |
+
for (i=0; i < len; i++) {
|
228 |
+
// #252; undo param space replacement
|
229 |
+
serialized[i] = serialized[i].replace(/\+/g,' ');
|
230 |
+
part = serialized[i].split('=');
|
231 |
+
result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]);
|
232 |
+
}
|
233 |
+
return result;
|
234 |
+
}
|
235 |
+
|
236 |
+
// XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
|
237 |
+
function fileUploadXhr(a) {
|
238 |
+
var formdata = new FormData();
|
239 |
+
|
240 |
+
for (var i=0; i < a.length; i++) {
|
241 |
+
formdata.append(a[i].name, a[i].value);
|
242 |
+
}
|
243 |
+
|
244 |
+
if (options.extraData) {
|
245 |
+
var serializedData = deepSerialize(options.extraData);
|
246 |
+
for (var p in serializedData)
|
247 |
+
if (serializedData.hasOwnProperty(p))
|
248 |
+
formdata.append(p, serializedData[p]);
|
249 |
+
}
|
250 |
+
|
251 |
+
options.data = null;
|
252 |
+
|
253 |
+
var s = $.extend(true, {}, $.ajaxSettings, options, {
|
254 |
+
contentType: false,
|
255 |
+
processData: false,
|
256 |
+
cache: false,
|
257 |
+
type: method || 'POST'
|
258 |
+
});
|
259 |
+
|
260 |
+
if (options.uploadProgress) {
|
261 |
+
// workaround because jqXHR does not expose upload property
|
262 |
+
s.xhr = function() {
|
263 |
+
var xhr = jQuery.ajaxSettings.xhr();
|
264 |
+
if (xhr.upload) {
|
265 |
+
xhr.upload.onprogress = function(event) {
|
266 |
+
var percent = 0;
|
267 |
+
var position = event.loaded || event.position; /*event.position is deprecated*/
|
268 |
+
var total = event.total;
|
269 |
+
if (event.lengthComputable) {
|
270 |
+
percent = Math.ceil(position / total * 100);
|
271 |
+
}
|
272 |
+
options.uploadProgress(event, position, total, percent);
|
273 |
+
};
|
274 |
+
}
|
275 |
+
return xhr;
|
276 |
+
};
|
277 |
+
}
|
278 |
+
|
279 |
+
s.data = null;
|
280 |
+
var beforeSend = s.beforeSend;
|
281 |
+
s.beforeSend = function(xhr, o) {
|
282 |
+
o.data = formdata;
|
283 |
+
if(beforeSend)
|
284 |
+
beforeSend.call(this, xhr, o);
|
285 |
+
};
|
286 |
+
return $.ajax(s);
|
287 |
+
}
|
288 |
+
|
289 |
+
// private function for handling file uploads (hat tip to YAHOO!)
|
290 |
+
function fileUploadIframe(a) {
|
291 |
+
var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
|
292 |
+
var useProp = !!$.fn.prop;
|
293 |
+
var deferred = $.Deferred();
|
294 |
+
|
295 |
+
if ($('[name=submit],[id=submit]', form).length) {
|
296 |
+
// if there is an input with a name or id of 'submit' then we won't be
|
297 |
+
// able to invoke the submit fn on the form (at least not x-browser)
|
298 |
+
alert('Error: Form elements must not have name or id of "submit".');
|
299 |
+
deferred.reject();
|
300 |
+
return deferred;
|
301 |
+
}
|
302 |
+
|
303 |
+
if (a) {
|
304 |
+
// ensure that every serialized input is still enabled
|
305 |
+
for (i=0; i < elements.length; i++) {
|
306 |
+
el = $(elements[i]);
|
307 |
+
if ( useProp )
|
308 |
+
el.prop('disabled', false);
|
309 |
+
else
|
310 |
+
el.removeAttr('disabled');
|
311 |
+
}
|
312 |
+
}
|
313 |
+
|
314 |
+
s = $.extend(true, {}, $.ajaxSettings, options);
|
315 |
+
s.context = s.context || s;
|
316 |
+
id = 'jqFormIO' + (new Date().getTime());
|
317 |
+
if (s.iframeTarget) {
|
318 |
+
$io = $(s.iframeTarget);
|
319 |
+
n = $io.attr('name');
|
320 |
+
if (!n)
|
321 |
+
$io.attr('name', id);
|
322 |
+
else
|
323 |
+
id = n;
|
324 |
+
}
|
325 |
+
else {
|
326 |
+
$io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
|
327 |
+
$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
|
328 |
+
}
|
329 |
+
io = $io[0];
|
330 |
+
|
331 |
+
|
332 |
+
xhr = { // mock object
|
333 |
+
aborted: 0,
|
334 |
+
responseText: null,
|
335 |
+
responseXML: null,
|
336 |
+
status: 0,
|
337 |
+
statusText: 'n/a',
|
338 |
+
getAllResponseHeaders: function() {},
|
339 |
+
getResponseHeader: function() {},
|
340 |
+
setRequestHeader: function() {},
|
341 |
+
abort: function(status) {
|
342 |
+
var e = (status === 'timeout' ? 'timeout' : 'aborted');
|
343 |
+
log('aborting upload... ' + e);
|
344 |
+
this.aborted = 1;
|
345 |
+
|
346 |
+
try { // #214, #257
|
347 |
+
if (io.contentWindow.document.execCommand) {
|
348 |
+
io.contentWindow.document.execCommand('Stop');
|
349 |
+
}
|
350 |
+
}
|
351 |
+
catch(ignore) {}
|
352 |
+
|
353 |
+
$io.attr('src', s.iframeSrc); // abort op in progress
|
354 |
+
xhr.error = e;
|
355 |
+
if (s.error)
|
356 |
+
s.error.call(s.context, xhr, e, status);
|
357 |
+
if (g)
|
358 |
+
$.event.trigger("ajaxError", [xhr, s, e]);
|
359 |
+
if (s.complete)
|
360 |
+
s.complete.call(s.context, xhr, e);
|
361 |
+
}
|
362 |
+
};
|
363 |
+
|
364 |
+
g = s.global;
|
365 |
+
// trigger ajax global events so that activity/block indicators work like normal
|
366 |
+
if (g && 0 === $.active++) {
|
367 |
+
$.event.trigger("ajaxStart");
|
368 |
+
}
|
369 |
+
if (g) {
|
370 |
+
$.event.trigger("ajaxSend", [xhr, s]);
|
371 |
+
}
|
372 |
+
|
373 |
+
if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
|
374 |
+
if (s.global) {
|
375 |
+
$.active--;
|
376 |
+
}
|
377 |
+
deferred.reject();
|
378 |
+
return deferred;
|
379 |
+
}
|
380 |
+
if (xhr.aborted) {
|
381 |
+
deferred.reject();
|
382 |
+
return deferred;
|
383 |
+
}
|
384 |
+
|
385 |
+
// add submitting element to data if we know it
|
386 |
+
sub = form.clk;
|
387 |
+
if (sub) {
|
388 |
+
n = sub.name;
|
389 |
+
if (n && !sub.disabled) {
|
390 |
+
s.extraData = s.extraData || {};
|
391 |
+
s.extraData[n] = sub.value;
|
392 |
+
if (sub.type == "image") {
|
393 |
+
s.extraData[n+'.x'] = form.clk_x;
|
394 |
+
s.extraData[n+'.y'] = form.clk_y;
|
395 |
+
}
|
396 |
+
}
|
397 |
+
}
|
398 |
+
|
399 |
+
var CLIENT_TIMEOUT_ABORT = 1;
|
400 |
+
var SERVER_ABORT = 2;
|
401 |
+
|
402 |
+
function getDoc(frame) {
|
403 |
+
var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
|
404 |
+
return doc;
|
405 |
+
}
|
406 |
+
|
407 |
+
// Rails CSRF hack (thanks to Yvan Barthelemy)
|
408 |
+
var csrf_token = $('meta[name=csrf-token]').attr('content');
|
409 |
+
var csrf_param = $('meta[name=csrf-param]').attr('content');
|
410 |
+
if (csrf_param && csrf_token) {
|
411 |
+
s.extraData = s.extraData || {};
|
412 |
+
s.extraData[csrf_param] = csrf_token;
|
413 |
+
}
|
414 |
+
|
415 |
+
// take a breath so that pending repaints get some cpu time before the upload starts
|
416 |
+
function doSubmit() {
|
417 |
+
// make sure form attrs are set
|
418 |
+
var t = $form.attr('target'), a = $form.attr('action');
|
419 |
+
|
420 |
+
// update form attrs in IE friendly way
|
421 |
+
form.setAttribute('target',id);
|
422 |
+
if (!method) {
|
423 |
+
form.setAttribute('method', 'POST');
|
424 |
+
}
|
425 |
+
if (a != s.url) {
|
426 |
+
form.setAttribute('action', s.url);
|
427 |
+
}
|
428 |
+
|
429 |
+
// ie borks in some cases when setting encoding
|
430 |
+
if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
|
431 |
+
$form.attr({
|
432 |
+
encoding: 'multipart/form-data',
|
433 |
+
enctype: 'multipart/form-data'
|
434 |
+
});
|
435 |
+
}
|
436 |
+
|
437 |
+
// support timout
|
438 |
+
if (s.timeout) {
|
439 |
+
timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
|
440 |
+
}
|
441 |
+
|
442 |
+
// look for server aborts
|
443 |
+
function checkState() {
|
444 |
+
try {
|
445 |
+
var state = getDoc(io).readyState;
|
446 |
+
log('state = ' + state);
|
447 |
+
if (state && state.toLowerCase() == 'uninitialized')
|
448 |
+
setTimeout(checkState,50);
|
449 |
+
}
|
450 |
+
catch(e) {
|
451 |
+
log('Server abort: ' , e, ' (', e.name, ')');
|
452 |
+
cb(SERVER_ABORT);
|
453 |
+
if (timeoutHandle)
|
454 |
+
clearTimeout(timeoutHandle);
|
455 |
+
timeoutHandle = undefined;
|
456 |
+
}
|
457 |
+
}
|
458 |
+
|
459 |
+
// add "extra" data to form if provided in options
|
460 |
+
var extraInputs = [];
|
461 |
+
try {
|
462 |
+
if (s.extraData) {
|
463 |
+
for (var n in s.extraData) {
|
464 |
+
if (s.extraData.hasOwnProperty(n)) {
|
465 |
+
// if using the $.param format that allows for multiple values with the same name
|
466 |
+
if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
|
467 |
+
extraInputs.push(
|
468 |
+
$('<input type="hidden" name="'+s.extraData[n].name+'">').val(s.extraData[n].value)
|
469 |
+
.appendTo(form)[0]);
|
470 |
+
} else {
|
471 |
+
extraInputs.push(
|
472 |
+
$('<input type="hidden" name="'+n+'">').val(s.extraData[n])
|
473 |
+
.appendTo(form)[0]);
|
474 |
+
}
|
475 |
+
}
|
476 |
+
}
|
477 |
+
}
|
478 |
+
|
479 |
+
if (!s.iframeTarget) {
|
480 |
+
// add iframe to doc and submit the form
|
481 |
+
$io.appendTo('body');
|
482 |
+
if (io.attachEvent)
|
483 |
+
io.attachEvent('onload', cb);
|
484 |
+
else
|
485 |
+
io.addEventListener('load', cb, false);
|
486 |
+
}
|
487 |
+
setTimeout(checkState,15);
|
488 |
+
form.submit();
|
489 |
+
}
|
490 |
+
finally {
|
491 |
+
// reset attrs and remove "extra" input elements
|
492 |
+
form.setAttribute('action',a);
|
493 |
+
if(t) {
|
494 |
+
form.setAttribute('target', t);
|
495 |
+
} else {
|
496 |
+
$form.removeAttr('target');
|
497 |
+
}
|
498 |
+
$(extraInputs).remove();
|
499 |
+
}
|
500 |
+
}
|
501 |
+
|
502 |
+
if (s.forceSync) {
|
503 |
+
doSubmit();
|
504 |
+
}
|
505 |
+
else {
|
506 |
+
setTimeout(doSubmit, 10); // this lets dom updates render
|
507 |
+
}
|
508 |
+
|
509 |
+
var data, doc, domCheckCount = 50, callbackProcessed;
|
510 |
+
|
511 |
+
function cb(e) {
|
512 |
+
if (xhr.aborted || callbackProcessed) {
|
513 |
+
return;
|
514 |
+
}
|
515 |
+
try {
|
516 |
+
doc = getDoc(io);
|
517 |
+
}
|
518 |
+
catch(ex) {
|
519 |
+
log('cannot access response document: ', ex);
|
520 |
+
e = SERVER_ABORT;
|
521 |
+
}
|
522 |
+
if (e === CLIENT_TIMEOUT_ABORT && xhr) {
|
523 |
+
xhr.abort('timeout');
|
524 |
+
deferred.reject(xhr, 'timeout');
|
525 |
+
return;
|
526 |
+
}
|
527 |
+
else if (e == SERVER_ABORT && xhr) {
|
528 |
+
xhr.abort('server abort');
|
529 |
+
deferred.reject(xhr, 'error', 'server abort');
|
530 |
+
return;
|
531 |
+
}
|
532 |
+
|
533 |
+
if (!doc || doc.location.href == s.iframeSrc) {
|
534 |
+
// response not received yet
|
535 |
+
if (!timedOut)
|
536 |
+
return;
|
537 |
+
}
|
538 |
+
if (io.detachEvent)
|
539 |
+
io.detachEvent('onload', cb);
|
540 |
+
else
|
541 |
+
io.removeEventListener('load', cb, false);
|
542 |
+
|
543 |
+
var status = 'success', errMsg;
|
544 |
+
try {
|
545 |
+
if (timedOut) {
|
546 |
+
throw 'timeout';
|
547 |
+
}
|
548 |
+
|
549 |
+
var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
|
550 |
+
log('isXml='+isXml);
|
551 |
+
if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
|
552 |
+
if (--domCheckCount) {
|
553 |
+
// in some browsers (Opera) the iframe DOM is not always traversable when
|
554 |
+
// the onload callback fires, so we loop a bit to accommodate
|
555 |
+
log('requeing onLoad callback, DOM not available');
|
556 |
+
setTimeout(cb, 250);
|
557 |
+
return;
|
558 |
+
}
|
559 |
+
// let this fall through because server response could be an empty document
|
560 |
+
//log('Could not access iframe DOM after mutiple tries.');
|
561 |
+
//throw 'DOMException: not available';
|
562 |
+
}
|
563 |
+
|
564 |
+
//log('response detected');
|
565 |
+
var docRoot = doc.body ? doc.body : doc.documentElement;
|
566 |
+
xhr.responseText = docRoot ? docRoot.innerHTML : null;
|
567 |
+
xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
|
568 |
+
if (isXml)
|
569 |
+
s.dataType = 'xml';
|
570 |
+
xhr.getResponseHeader = function(header){
|
571 |
+
var headers = {'content-type': s.dataType};
|
572 |
+
return headers[header];
|
573 |
+
};
|
574 |
+
// support for XHR 'status' & 'statusText' emulation :
|
575 |
+
if (docRoot) {
|
576 |
+
xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
|
577 |
+
xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
|
578 |
+
}
|
579 |
+
|
580 |
+
var dt = (s.dataType || '').toLowerCase();
|
581 |
+
var scr = /(json|script|text)/.test(dt);
|
582 |
+
if (scr || s.textarea) {
|
583 |
+
// see if user embedded response in textarea
|
584 |
+
var ta = doc.getElementsByTagName('textarea')[0];
|
585 |
+
if (ta) {
|
586 |
+
xhr.responseText = ta.value;
|
587 |
+
// support for XHR 'status' & 'statusText' emulation :
|
588 |
+
xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
|
589 |
+
xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
|
590 |
+
}
|
591 |
+
else if (scr) {
|
592 |
+
// account for browsers injecting pre around json response
|
593 |
+
var pre = doc.getElementsByTagName('pre')[0];
|
594 |
+
var b = doc.getElementsByTagName('body')[0];
|
595 |
+
if (pre) {
|
596 |
+
xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
|
597 |
+
}
|
598 |
+
else if (b) {
|
599 |
+
xhr.responseText = b.textContent ? b.textContent : b.innerText;
|
600 |
+
}
|
601 |
+
}
|
602 |
+
}
|
603 |
+
else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
|
604 |
+
xhr.responseXML = toXml(xhr.responseText);
|
605 |
+
}
|
606 |
+
|
607 |
+
try {
|
608 |
+
data = httpData(xhr, dt, s);
|
609 |
+
}
|
610 |
+
catch (e) {
|
611 |
+
status = 'parsererror';
|
612 |
+
xhr.error = errMsg = (e || status);
|
613 |
+
}
|
614 |
+
}
|
615 |
+
catch (e) {
|
616 |
+
log('error caught: ',e);
|
617 |
+
status = 'error';
|
618 |
+
xhr.error = errMsg = (e || status);
|
619 |
+
}
|
620 |
+
|
621 |
+
if (xhr.aborted) {
|
622 |
+
log('upload aborted');
|
623 |
+
status = null;
|
624 |
+
}
|
625 |
+
|
626 |
+
if (xhr.status) { // we've set xhr.status
|
627 |
+
status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
|
628 |
+
}
|
629 |
+
|
630 |
+
// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
|
631 |
+
if (status === 'success') {
|
632 |
+
if (s.success)
|
633 |
+
s.success.call(s.context, data, 'success', xhr);
|
634 |
+
deferred.resolve(xhr.responseText, 'success', xhr);
|
635 |
+
if (g)
|
636 |
+
$.event.trigger("ajaxSuccess", [xhr, s]);
|
637 |
+
}
|
638 |
+
else if (status) {
|
639 |
+
if (errMsg === undefined)
|
640 |
+
errMsg = xhr.statusText;
|
641 |
+
if (s.error)
|
642 |
+
s.error.call(s.context, xhr, status, errMsg);
|
643 |
+
deferred.reject(xhr, 'error', errMsg);
|
644 |
+
if (g)
|
645 |
+
$.event.trigger("ajaxError", [xhr, s, errMsg]);
|
646 |
+
}
|
647 |
+
|
648 |
+
if (g)
|
649 |
+
$.event.trigger("ajaxComplete", [xhr, s]);
|
650 |
+
|
651 |
+
if (g && ! --$.active) {
|
652 |
+
$.event.trigger("ajaxStop");
|
653 |
+
}
|
654 |
+
|
655 |
+
if (s.complete)
|
656 |
+
s.complete.call(s.context, xhr, status);
|
657 |
+
|
658 |
+
callbackProcessed = true;
|
659 |
+
if (s.timeout)
|
660 |
+
clearTimeout(timeoutHandle);
|
661 |
+
|
662 |
+
// clean up
|
663 |
+
setTimeout(function() {
|
664 |
+
if (!s.iframeTarget)
|
665 |
+
$io.remove();
|
666 |
+
xhr.responseXML = null;
|
667 |
+
}, 100);
|
668 |
+
}
|
669 |
+
|
670 |
+
var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
|
671 |
+
if (window.ActiveXObject) {
|
672 |
+
doc = new ActiveXObject('Microsoft.XMLDOM');
|
673 |
+
doc.async = 'false';
|
674 |
+
doc.loadXML(s);
|
675 |
+
}
|
676 |
+
else {
|
677 |
+
doc = (new DOMParser()).parseFromString(s, 'text/xml');
|
678 |
+
}
|
679 |
+
return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
|
680 |
+
};
|
681 |
+
var parseJSON = $.parseJSON || function(s) {
|
682 |
+
/*jslint evil:true */
|
683 |
+
return window['eval']('(' + s + ')');
|
684 |
+
};
|
685 |
+
|
686 |
+
var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
|
687 |
+
|
688 |
+
var ct = xhr.getResponseHeader('content-type') || '',
|
689 |
+
xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
|
690 |
+
data = xml ? xhr.responseXML : xhr.responseText;
|
691 |
+
|
692 |
+
if (xml && data.documentElement.nodeName === 'parsererror') {
|
693 |
+
if ($.error)
|
694 |
+
$.error('parsererror');
|
695 |
+
}
|
696 |
+
if (s && s.dataFilter) {
|
697 |
+
data = s.dataFilter(data, type);
|
698 |
+
}
|
699 |
+
if (typeof data === 'string') {
|
700 |
+
if (type === 'json' || !type && ct.indexOf('json') >= 0) {
|
701 |
+
data = parseJSON(data);
|
702 |
+
} else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
|
703 |
+
$.globalEval(data);
|
704 |
+
}
|
705 |
+
}
|
706 |
+
return data;
|
707 |
+
};
|
708 |
+
|
709 |
+
return deferred;
|
710 |
+
}
|
711 |
};
|
712 |
|
713 |
/**
|
716 |
* The advantages of using this method instead of ajaxSubmit() are:
|
717 |
*
|
718 |
* 1: This method will include coordinates for <input type="image" /> elements (if the element
|
719 |
+
* is used to submit the form).
|
720 |
* 2. This method will include the submit element's name/value data (for the element that was
|
721 |
+
* used to submit the form).
|
722 |
* 3. This method binds the submit() method to the form for you.
|
723 |
*
|
724 |
* The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
|
726 |
* the form itself.
|
727 |
*/
|
728 |
$.fn.ajaxForm = function(options) {
|
729 |
+
options = options || {};
|
730 |
+
options.delegation = options.delegation && $.isFunction($.fn.on);
|
731 |
+
|
732 |
+
// in jQuery 1.3+ we can fix mistakes with the ready state
|
733 |
+
if (!options.delegation && this.length === 0) {
|
734 |
+
var o = { s: this.selector, c: this.context };
|
735 |
+
if (!$.isReady && o.s) {
|
736 |
+
log('DOM not ready, queuing ajaxForm');
|
737 |
+
$(function() {
|
738 |
+
$(o.s,o.c).ajaxForm(options);
|
739 |
+
});
|
740 |
+
return this;
|
741 |
+
}
|
742 |
+
// is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
|
743 |
+
log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
|
744 |
+
return this;
|
745 |
+
}
|
746 |
+
|
747 |
+
if ( options.delegation ) {
|
748 |
+
$(document)
|
749 |
+
.off('submit.form-plugin', this.selector, doAjaxSubmit)
|
750 |
+
.off('click.form-plugin', this.selector, captureSubmittingElement)
|
751 |
+
.on('submit.form-plugin', this.selector, options, doAjaxSubmit)
|
752 |
+
.on('click.form-plugin', this.selector, options, captureSubmittingElement);
|
753 |
+
return this;
|
754 |
+
}
|
755 |
+
|
756 |
+
return this.ajaxFormUnbind()
|
757 |
+
.bind('submit.form-plugin', options, doAjaxSubmit)
|
758 |
+
.bind('click.form-plugin', options, captureSubmittingElement);
|
|
|
759 |
};
|
760 |
|
761 |
+
// private event handlers
|
762 |
+
function doAjaxSubmit(e) {
|
763 |
+
/*jshint validthis:true */
|
764 |
+
var options = e.data;
|
765 |
+
if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
|
766 |
+
e.preventDefault();
|
767 |
+
$(this).ajaxSubmit(options);
|
768 |
+
}
|
769 |
+
}
|
770 |
+
|
771 |
+
function captureSubmittingElement(e) {
|
772 |
+
/*jshint validthis:true */
|
773 |
+
var target = e.target;
|
774 |
+
var $el = $(target);
|
775 |
+
if (!($el.is("[type=submit],[type=image]"))) {
|
776 |
+
// is this a child element of the submit el? (ex: a span within a button)
|
777 |
+
var t = $el.closest('[type=submit]');
|
778 |
+
if (t.length === 0) {
|
779 |
+
return;
|
780 |
+
}
|
781 |
+
target = t[0];
|
782 |
+
}
|
783 |
+
var form = this;
|
784 |
+
form.clk = target;
|
785 |
+
if (target.type == 'image') {
|
786 |
+
if (e.offsetX !== undefined) {
|
787 |
+
form.clk_x = e.offsetX;
|
788 |
+
form.clk_y = e.offsetY;
|
789 |
+
} else if (typeof $.fn.offset == 'function') {
|
790 |
+
var offset = $el.offset();
|
791 |
+
form.clk_x = e.pageX - offset.left;
|
792 |
+
form.clk_y = e.pageY - offset.top;
|
793 |
+
} else {
|
794 |
+
form.clk_x = e.pageX - target.offsetLeft;
|
795 |
+
form.clk_y = e.pageY - target.offsetTop;
|
796 |
+
}
|
797 |
+
}
|
798 |
+
// clear form vars
|
799 |
+
setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
|
800 |
+
}
|
801 |
+
|
802 |
+
|
803 |
// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
|
804 |
$.fn.ajaxFormUnbind = function() {
|
805 |
+
return this.unbind('submit.form-plugin click.form-plugin');
|
806 |
};
|
807 |
|
808 |
/**
|
816 |
* It is this array that is passed to pre-submit callback functions provided to the
|
817 |
* ajaxSubmit() and ajaxForm() methods.
|
818 |
*/
|
819 |
+
$.fn.formToArray = function(semantic, elements) {
|
820 |
+
var a = [];
|
821 |
+
if (this.length === 0) {
|
822 |
+
return a;
|
823 |
+
}
|
824 |
+
|
825 |
+
var form = this[0];
|
826 |
+
var els = semantic ? form.getElementsByTagName('*') : form.elements;
|
827 |
+
if (!els) {
|
828 |
+
return a;
|
829 |
+
}
|
830 |
+
|
831 |
+
var i,j,n,v,el,max,jmax;
|
832 |
+
for(i=0, max=els.length; i < max; i++) {
|
833 |
+
el = els[i];
|
834 |
+
n = el.name;
|
835 |
+
if (!n) {
|
836 |
+
continue;
|
837 |
+
}
|
838 |
+
|
839 |
+
if (semantic && form.clk && el.type == "image") {
|
840 |
+
// handle image inputs on the fly when semantic == true
|
841 |
+
if(!el.disabled && form.clk == el) {
|
842 |
+
a.push({name: n, value: $(el).val(), type: el.type });
|
843 |
+
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
|
844 |
+
}
|
845 |
+
continue;
|
846 |
+
}
|
847 |
+
|
848 |
+
v = $.fieldValue(el, true);
|
849 |
+
if (v && v.constructor == Array) {
|
850 |
+
if (elements)
|
851 |
+
elements.push(el);
|
852 |
+
for(j=0, jmax=v.length; j < jmax; j++) {
|
853 |
+
a.push({name: n, value: v[j]});
|
854 |
+
}
|
855 |
+
}
|
856 |
+
else if (feature.fileapi && el.type == 'file' && !el.disabled) {
|
857 |
+
if (elements)
|
858 |
+
elements.push(el);
|
859 |
+
var files = el.files;
|
860 |
+
if (files.length) {
|
861 |
+
for (j=0; j < files.length; j++) {
|
862 |
+
a.push({name: n, value: files[j], type: el.type});
|
863 |
+
}
|
864 |
+
}
|
865 |
+
else {
|
866 |
+
// #180
|
867 |
+
a.push({ name: n, value: '', type: el.type });
|
868 |
+
}
|
869 |
+
}
|
870 |
+
else if (v !== null && typeof v != 'undefined') {
|
871 |
+
if (elements)
|
872 |
+
elements.push(el);
|
873 |
+
a.push({name: n, value: v, type: el.type, required: el.required});
|
874 |
+
}
|
875 |
+
}
|
876 |
+
|
877 |
+
if (!semantic && form.clk) {
|
878 |
+
// input type=='image' are not found in elements array! handle it here
|
879 |
+
var $input = $(form.clk), input = $input[0];
|
880 |
+
n = input.name;
|
881 |
+
if (n && !input.disabled && input.type == 'image') {
|
882 |
+
a.push({name: n, value: $input.val()});
|
883 |
+
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
|
884 |
+
}
|
885 |
+
}
|
886 |
+
return a;
|
887 |
};
|
888 |
|
889 |
/**
|
891 |
* in the format: name1=value1&name2=value2
|
892 |
*/
|
893 |
$.fn.formSerialize = function(semantic) {
|
894 |
+
//hand off to jQuery.param for proper encoding
|
895 |
+
return $.param(this.formToArray(semantic));
|
896 |
};
|
897 |
|
898 |
/**
|
900 |
* This method will return a string in the format: name1=value1&name2=value2
|
901 |
*/
|
902 |
$.fn.fieldSerialize = function(successful) {
|
903 |
+
var a = [];
|
904 |
+
this.each(function() {
|
905 |
+
var n = this.name;
|
906 |
+
if (!n) {
|
907 |
+
return;
|
908 |
+
}
|
909 |
+
var v = $.fieldValue(this, successful);
|
910 |
+
if (v && v.constructor == Array) {
|
911 |
+
for (var i=0,max=v.length; i < max; i++) {
|
912 |
+
a.push({name: n, value: v[i]});
|
913 |
+
}
|
914 |
+
}
|
915 |
+
else if (v !== null && typeof v != 'undefined') {
|
916 |
+
a.push({name: this.name, value: v});
|
917 |
+
}
|
918 |
+
});
|
919 |
+
//hand off to jQuery.param for proper encoding
|
920 |
+
return $.param(a);
|
921 |
};
|
922 |
|
923 |
/**
|
924 |
* Returns the value(s) of the element in the matched set. For example, consider the following form:
|
925 |
*
|
926 |
* <form><fieldset>
|
927 |
+
* <input name="A" type="text" />
|
928 |
+
* <input name="A" type="text" />
|
929 |
+
* <input name="B" type="checkbox" value="B1" />
|
930 |
+
* <input name="B" type="checkbox" value="B2"/>
|
931 |
+
* <input name="C" type="radio" value="C1" />
|
932 |
+
* <input name="C" type="radio" value="C2" />
|
933 |
* </fieldset></form>
|
934 |
*
|
935 |
+
* var v = $('input[type=text]').fieldValue();
|
936 |
* // if no values are entered into the text inputs
|
937 |
* v == ['','']
|
938 |
* // if values entered into the text inputs are 'foo' and 'bar'
|
939 |
* v == ['foo','bar']
|
940 |
*
|
941 |
+
* var v = $('input[type=checkbox]').fieldValue();
|
942 |
* // if neither checkbox is checked
|
943 |
* v === undefined
|
944 |
* // if both checkboxes are checked
|
945 |
* v == ['B1', 'B2']
|
946 |
*
|
947 |
+
* var v = $('input[type=radio]').fieldValue();
|
948 |
* // if neither radio is checked
|
949 |
* v === undefined
|
950 |
* // if first radio is checked
|
956 |
* for each element is returned.
|
957 |
*
|
958 |
* Note: This method *always* returns an array. If no valid value can be determined the
|
959 |
+
* array will be empty, otherwise it will contain one or more values.
|
960 |
*/
|
961 |
$.fn.fieldValue = function(successful) {
|
962 |
+
for (var val=[], i=0, max=this.length; i < max; i++) {
|
963 |
+
var el = this[i];
|
964 |
+
var v = $.fieldValue(el, successful);
|
965 |
+
if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
|
966 |
+
continue;
|
967 |
+
}
|
968 |
+
if (v.constructor == Array)
|
969 |
+
$.merge(val, v);
|
970 |
+
else
|
971 |
+
val.push(v);
|
972 |
+
}
|
973 |
+
return val;
|
974 |
};
|
975 |
|
976 |
/**
|
977 |
* Returns the value of the field element.
|
978 |
*/
|
979 |
$.fieldValue = function(el, successful) {
|
980 |
+
var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
|
981 |
+
if (successful === undefined) {
|
982 |
+
successful = true;
|
983 |
+
}
|
984 |
+
|
985 |
+
if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
|
986 |
+
(t == 'checkbox' || t == 'radio') && !el.checked ||
|
987 |
+
(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
|
988 |
+
tag == 'select' && el.selectedIndex == -1)) {
|
989 |
+
return null;
|
990 |
+
}
|
991 |
+
|
992 |
+
if (tag == 'select') {
|
993 |
+
var index = el.selectedIndex;
|
994 |
+
if (index < 0) {
|
995 |
+
return null;
|
996 |
+
}
|
997 |
+
var a = [], ops = el.options;
|
998 |
+
var one = (t == 'select-one');
|
999 |
+
var max = (one ? index+1 : ops.length);
|
1000 |
+
for(var i=(one ? index : 0); i < max; i++) {
|
1001 |
+
var op = ops[i];
|
1002 |
+
if (op.selected) {
|
1003 |
+
var v = op.value;
|
1004 |
+
if (!v) { // extra pain for IE...
|
1005 |
+
v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
|
1006 |
+
}
|
1007 |
+
if (one) {
|
1008 |
+
return v;
|
1009 |
+
}
|
1010 |
+
a.push(v);
|
1011 |
+
}
|
1012 |
+
}
|
1013 |
+
return a;
|
1014 |
+
}
|
1015 |
+
return $(el).val();
|
1016 |
};
|
1017 |
|
1018 |
/**
|
1023 |
* - inputs of type submit, button, reset, and hidden will *not* be effected
|
1024 |
* - button elements will *not* be effected
|
1025 |
*/
|
1026 |
+
$.fn.clearForm = function(includeHidden) {
|
1027 |
+
return this.each(function() {
|
1028 |
+
$('input,select,textarea', this).clearFields(includeHidden);
|
1029 |
+
});
|
1030 |
};
|
1031 |
|
1032 |
/**
|
1033 |
* Clears the selected form elements.
|
1034 |
*/
|
1035 |
+
$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
|
1036 |
+
var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
|
1037 |
+
return this.each(function() {
|
1038 |
+
var t = this.type, tag = this.tagName.toLowerCase();
|
1039 |
+
if (re.test(t) || tag == 'textarea') {
|
1040 |
+
this.value = '';
|
1041 |
+
}
|
1042 |
+
else if (t == 'checkbox' || t == 'radio') {
|
1043 |
+
this.checked = false;
|
1044 |
+
}
|
1045 |
+
else if (tag == 'select') {
|
1046 |
+
this.selectedIndex = -1;
|
1047 |
+
}
|
1048 |
+
else if (t == "file") {
|
1049 |
+
if ($.browser.msie) {
|
1050 |
+
$(this).replaceWith($(this).clone());
|
1051 |
+
} else {
|
1052 |
+
$(this).val('');
|
1053 |
+
}
|
1054 |
+
}
|
1055 |
+
else if (includeHidden) {
|
1056 |
+
// includeHidden can be the value true, or it can be a selector string
|
1057 |
+
// indicating a special test; for example:
|
1058 |
+
// $('#myForm').clearForm('.special:hidden')
|
1059 |
+
// the above would clean hidden inputs that have the class of 'special'
|
1060 |
+
if ( (includeHidden === true && /hidden/.test(t)) ||
|
1061 |
+
(typeof includeHidden == 'string' && $(this).is(includeHidden)) )
|
1062 |
+
this.value = '';
|
1063 |
+
}
|
1064 |
+
});
|
1065 |
};
|
1066 |
|
1067 |
/**
|
1068 |
* Resets the form data. Causes all form elements to be reset to their original value.
|
1069 |
*/
|
1070 |
$.fn.resetForm = function() {
|
1071 |
+
return this.each(function() {
|
1072 |
+
// guard against an input with the name of 'reset'
|
1073 |
+
// note that IE reports the reset function as an 'object'
|
1074 |
+
if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
|
1075 |
+
this.reset();
|
1076 |
+
}
|
1077 |
+
});
|
1078 |
};
|
1079 |
|
1080 |
/**
|
1081 |
* Enables or disables any matching elements.
|
1082 |
*/
|
1083 |
$.fn.enable = function(b) {
|
1084 |
+
if (b === undefined) {
|
1085 |
+
b = true;
|
1086 |
+
}
|
1087 |
+
return this.each(function() {
|
1088 |
+
this.disabled = !b;
|
1089 |
+
});
|
1090 |
};
|
1091 |
|
1092 |
/**
|
1094 |
* selects/deselects and matching option elements.
|
1095 |
*/
|
1096 |
$.fn.selected = function(select) {
|
1097 |
+
if (select === undefined) {
|
1098 |
+
select = true;
|
1099 |
+
}
|
1100 |
+
return this.each(function() {
|
1101 |
+
var t = this.type;
|
1102 |
+
if (t == 'checkbox' || t == 'radio') {
|
1103 |
+
this.checked = select;
|
1104 |
+
}
|
1105 |
+
else if (this.tagName.toLowerCase() == 'option') {
|
1106 |
+
var $sel = $(this).parent('select');
|
1107 |
+
if (select && $sel[0] && $sel[0].type == 'select-one') {
|
1108 |
+
// deselect all other options
|
1109 |
+
$sel.find('option').selected(false);
|
1110 |
+
}
|
1111 |
+
this.selected = select;
|
1112 |
+
}
|
1113 |
+
});
|
1114 |
};
|
1115 |
|
1116 |
+
// expose debug var
|
1117 |
+
$.fn.ajaxSubmit.debug = false;
|
1118 |
+
|
1119 |
// helper fn for console logging
|
|
|
1120 |
function log() {
|
1121 |
+
if (!$.fn.ajaxSubmit.debug)
|
1122 |
+
return;
|
1123 |
+
var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
|
1124 |
+
if (window.console && window.console.log) {
|
1125 |
+
window.console.log(msg);
|
1126 |
+
}
|
1127 |
+
else if (window.opera && window.opera.postError) {
|
1128 |
+
window.opera.postError(msg);
|
1129 |
+
}
|
1130 |
+
}
|
1131 |
|
1132 |
})(jQuery);
|
js/{jquery.json-1.3.js → jquery.json.js}
RENAMED
File without changes
|
js/jquery.qtip.js
ADDED
@@ -0,0 +1,3365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*! qTip2 - Pretty powerful tooltips - v2.0.0 - 2012-09-10
|
2 |
+
* http://craigsworks.com/projects/qtip2/
|
3 |
+
* Copyright (c) 2012 Craig Michael Thompson; Licensed MIT, GPL */
|
4 |
+
|
5 |
+
/*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true */
|
6 |
+
/*global window: false, jQuery: false, console: false, define: false */
|
7 |
+
|
8 |
+
/* Cache window, document, undefined */
|
9 |
+
(function( window, document, undefined ) {
|
10 |
+
|
11 |
+
// Uses AMD or browser globals to create a jQuery plugin.
|
12 |
+
(function( factory ) {
|
13 |
+
"use strict";
|
14 |
+
if(typeof define === 'function' && define.amd) {
|
15 |
+
define(['jquery'], factory);
|
16 |
+
}
|
17 |
+
else if(jQuery && !jQuery.fn.qtip) {
|
18 |
+
factory(jQuery);
|
19 |
+
}
|
20 |
+
}
|
21 |
+
(function($) {
|
22 |
+
/* This currently causes issues with Safari 6, so for it's disabled */
|
23 |
+
//"use strict"; // (Dis)able ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
|
24 |
+
|
25 |
+
// Munge the primitives - Paul Irish tip
|
26 |
+
var TRUE = true,
|
27 |
+
FALSE = false,
|
28 |
+
NULL = null,
|
29 |
+
|
30 |
+
// Side names and other stuff
|
31 |
+
X = 'x', Y = 'y',
|
32 |
+
WIDTH = 'width',
|
33 |
+
HEIGHT = 'height',
|
34 |
+
TOP = 'top',
|
35 |
+
LEFT = 'left',
|
36 |
+
BOTTOM = 'bottom',
|
37 |
+
RIGHT = 'right',
|
38 |
+
CENTER = 'center',
|
39 |
+
FLIP = 'flip',
|
40 |
+
FLIPINVERT = 'flipinvert',
|
41 |
+
SHIFT = 'shift',
|
42 |
+
|
43 |
+
// Shortcut vars
|
44 |
+
QTIP, PLUGINS, MOUSE,
|
45 |
+
usedIDs = {},
|
46 |
+
uitooltip = 'ui-tooltip',
|
47 |
+
widget = 'ui-widget',
|
48 |
+
disabled = 'ui-state-disabled',
|
49 |
+
selector = 'div.qtip.'+uitooltip,
|
50 |
+
defaultClass = uitooltip + '-default',
|
51 |
+
focusClass = uitooltip + '-focus',
|
52 |
+
hoverClass = uitooltip + '-hover',
|
53 |
+
replaceSuffix = '_replacedByqTip',
|
54 |
+
oldtitle = 'oldtitle',
|
55 |
+
trackingBound,
|
56 |
+
redrawContainer;
|
57 |
+
|
58 |
+
/*
|
59 |
+
* redraw() container for width/height calculations
|
60 |
+
*/
|
61 |
+
redrawContainer = $('<div/>', { id: 'qtip-rcontainer' });
|
62 |
+
$(function() { redrawContainer.appendTo(document.body); });
|
63 |
+
|
64 |
+
|
65 |
+
|
66 |
+
// Option object sanitizer
|
67 |
+
function sanitizeOptions(opts)
|
68 |
+
{
|
69 |
+
var invalid = function(a) { return a === NULL || 'object' !== typeof a; },
|
70 |
+
invalidContent = function(c) { return !$.isFunction(c) && ((!c && !c.attr) || c.length < 1 || ('object' === typeof c && !c.jquery)); };
|
71 |
+
|
72 |
+
if(!opts || 'object' !== typeof opts) { return FALSE; }
|
73 |
+
|
74 |
+
if(invalid(opts.metadata)) {
|
75 |
+
opts.metadata = { type: opts.metadata };
|
76 |
+
}
|
77 |
+
|
78 |
+
if('content' in opts) {
|
79 |
+
if(invalid(opts.content) || opts.content.jquery) {
|
80 |
+
opts.content = { text: opts.content };
|
81 |
+
}
|
82 |
+
|
83 |
+
if(invalidContent(opts.content.text || FALSE)) {
|
84 |
+
opts.content.text = FALSE;
|
85 |
+
}
|
86 |
+
|
87 |
+
if('title' in opts.content) {
|
88 |
+
if(invalid(opts.content.title)) {
|
89 |
+
opts.content.title = { text: opts.content.title };
|
90 |
+
}
|
91 |
+
|
92 |
+
if(invalidContent(opts.content.title.text || FALSE)) {
|
93 |
+
opts.content.title.text = FALSE;
|
94 |
+
}
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
if('position' in opts && invalid(opts.position)) {
|
99 |
+
opts.position = { my: opts.position, at: opts.position };
|
100 |
+
}
|
101 |
+
|
102 |
+
if('show' in opts && invalid(opts.show)) {
|
103 |
+
opts.show = opts.show.jquery ? { target: opts.show } : { event: opts.show };
|
104 |
+
}
|
105 |
+
|
106 |
+
if('hide' in opts && invalid(opts.hide)) {
|
107 |
+
opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
|
108 |
+
}
|
109 |
+
|
110 |
+
if('style' in opts && invalid(opts.style)) {
|
111 |
+
opts.style = { classes: opts.style };
|
112 |
+
}
|
113 |
+
|
114 |
+
// Sanitize plugin options
|
115 |
+
$.each(PLUGINS, function() {
|
116 |
+
if(this.sanitize) { this.sanitize(opts); }
|
117 |
+
});
|
118 |
+
|
119 |
+
return opts;
|
120 |
+
}
|
121 |
+
|
122 |
+
/*
|
123 |
+
* Core plugin implementation
|
124 |
+
*/
|
125 |
+
function QTip(target, options, id, attr)
|
126 |
+
{
|
127 |
+
// Declare this reference
|
128 |
+
var self = this,
|
129 |
+
docBody = document.body,
|
130 |
+
tooltipID = uitooltip + '-' + id,
|
131 |
+
isPositioning = 0,
|
132 |
+
isDrawing = 0,
|
133 |
+
tooltip = $(),
|
134 |
+
namespace = '.qtip-' + id,
|
135 |
+
elements, cache;
|
136 |
+
|
137 |
+
// Setup class attributes
|
138 |
+
self.id = id;
|
139 |
+
self.rendered = FALSE;
|
140 |
+
self.destroyed = FALSE;
|
141 |
+
self.elements = elements = { target: target };
|
142 |
+
self.timers = { img: {} };
|
143 |
+
self.options = options;
|
144 |
+
self.checks = {};
|
145 |
+
self.plugins = {};
|
146 |
+
self.cache = cache = {
|
147 |
+
event: {},
|
148 |
+
target: $(),
|
149 |
+
disabled: FALSE,
|
150 |
+
attr: attr,
|
151 |
+
onTarget: FALSE,
|
152 |
+
lastClass: ''
|
153 |
+
};
|
154 |
+
|
155 |
+
/*
|
156 |
+
* Private core functions
|
157 |
+
*/
|
158 |
+
function convertNotation(notation)
|
159 |
+
{
|
160 |
+
var i = 0, obj, option = options,
|
161 |
+
|
162 |
+
// Split notation into array
|
163 |
+
levels = notation.split('.');
|
164 |
+
|
165 |
+
// Loop through
|
166 |
+
while( option = option[ levels[i++] ] ) {
|
167 |
+
if(i < levels.length) { obj = option; }
|
168 |
+
}
|
169 |
+
|
170 |
+
return [obj || options, levels.pop()];
|
171 |
+
}
|
172 |
+
|
173 |
+
function triggerEvent(type, args, event) {
|
174 |
+
var callback = $.Event('tooltip'+type);
|
175 |
+
callback.originalEvent = (event ? $.extend({}, event) : NULL) || cache.event || NULL;
|
176 |
+
tooltip.trigger(callback, [self].concat(args || []));
|
177 |
+
|
178 |
+
return !callback.isDefaultPrevented();
|
179 |
+
}
|
180 |
+
|
181 |
+
function setWidget()
|
182 |
+
{
|
183 |
+
var on = options.style.widget;
|
184 |
+
|
185 |
+
tooltip.toggleClass('ui-helper-reset '+widget, on).toggleClass(defaultClass, options.style.def && !on);
|
186 |
+
|
187 |
+
if(elements.content) {
|
188 |
+
elements.content.toggleClass(widget+'-content', on);
|
189 |
+
}
|
190 |
+
|
191 |
+
if(elements.titlebar) {
|
192 |
+
elements.titlebar.toggleClass(widget+'-header', on);
|
193 |
+
}
|
194 |
+
if(elements.button) {
|
195 |
+
elements.button.toggleClass(uitooltip+'-icon', !on);
|
196 |
+
}
|
197 |
+
}
|
198 |
+
|
199 |
+
function removeTitle(reposition)
|
200 |
+
{
|
201 |
+
if(elements.title) {
|
202 |
+
elements.titlebar.remove();
|
203 |
+
elements.titlebar = elements.title = elements.button = NULL;
|
204 |
+
|
205 |
+
// Reposition if enabled
|
206 |
+
if(reposition !== FALSE) { self.reposition(); }
|
207 |
+
}
|
208 |
+
}
|
209 |
+
|
210 |
+
function createButton()
|
211 |
+
{
|
212 |
+
var button = options.content.title.button,
|
213 |
+
isString = typeof button === 'string',
|
214 |
+
close = isString ? button : 'Close tooltip';
|
215 |
+
|
216 |
+
if(elements.button) { elements.button.remove(); }
|
217 |
+
|
218 |
+
// Use custom button if one was supplied by user, else use default
|
219 |
+
if(button.jquery) {
|
220 |
+
elements.button = button;
|
221 |
+
}
|
222 |
+
else {
|
223 |
+
elements.button = $('<a />', {
|
224 |
+
'class': 'ui-state-default ui-tooltip-close ' + (options.style.widget ? '' : uitooltip+'-icon'),
|
225 |
+
'title': close,
|
226 |
+
'aria-label': close
|
227 |
+
})
|
228 |
+
.prepend(
|
229 |
+
$('<span />', {
|
230 |
+
'class': 'ui-icon ui-icon-close',
|
231 |
+
'html': '×'
|
232 |
+
})
|
233 |
+
);
|
234 |
+
}
|
235 |
+
|
236 |
+
// Create button and setup attributes
|
237 |
+
elements.button.appendTo(elements.titlebar)
|
238 |
+
.attr('role', 'button')
|
239 |
+
.click(function(event) {
|
240 |
+
if(!tooltip.hasClass(disabled)) { self.hide(event); }
|
241 |
+
return FALSE;
|
242 |
+
});
|
243 |
+
|
244 |
+
// Redraw the tooltip when we're done
|
245 |
+
self.redraw();
|
246 |
+
}
|
247 |
+
|
248 |
+
function createTitle()
|
249 |
+
{
|
250 |
+
var id = tooltipID+'-title';
|
251 |
+
|
252 |
+
// Destroy previous title element, if present
|
253 |
+
if(elements.titlebar) { removeTitle(); }
|
254 |
+
|
255 |
+
// Create title bar and title elements
|
256 |
+
elements.titlebar = $('<div />', {
|
257 |
+
'class': uitooltip + '-titlebar ' + (options.style.widget ? 'ui-widget-header' : '')
|
258 |
+
})
|
259 |
+
.append(
|
260 |
+
elements.title = $('<div />', {
|
261 |
+
'id': id,
|
262 |
+
'class': uitooltip + '-title',
|
263 |
+
'aria-atomic': TRUE
|
264 |
+
})
|
265 |
+
)
|
266 |
+
.insertBefore(elements.content)
|
267 |
+
|
268 |
+
// Button-specific events
|
269 |
+
.delegate('.ui-tooltip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
|
270 |
+
$(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
|
271 |
+
})
|
272 |
+
.delegate('.ui-tooltip-close', 'mouseover mouseout', function(event){
|
273 |
+
$(this).toggleClass('ui-state-hover', event.type === 'mouseover');
|
274 |
+
});
|
275 |
+
|
276 |
+
// Create button if enabled
|
277 |
+
if(options.content.title.button) { createButton(); }
|
278 |
+
|
279 |
+
// Redraw the tooltip dimensions if it's rendered
|
280 |
+
else if(self.rendered){ self.redraw(); }
|
281 |
+
}
|
282 |
+
|
283 |
+
function updateButton(button)
|
284 |
+
{
|
285 |
+
var elem = elements.button,
|
286 |
+
title = elements.title;
|
287 |
+
|
288 |
+
// Make sure tooltip is rendered and if not, return
|
289 |
+
if(!self.rendered) { return FALSE; }
|
290 |
+
|
291 |
+
if(!button) {
|
292 |
+
elem.remove();
|
293 |
+
}
|
294 |
+
else {
|
295 |
+
if(!title) {
|
296 |
+
createTitle();
|
297 |
+
}
|
298 |
+
createButton();
|
299 |
+
}
|
300 |
+
}
|
301 |
+
|
302 |
+
function updateTitle(content, reposition)
|
303 |
+
{
|
304 |
+
var elem = elements.title;
|
305 |
+
|
306 |
+
// Make sure tooltip is rendered and if not, return
|
307 |
+
if(!self.rendered || !content) { return FALSE; }
|
308 |
+
|
309 |
+
// Use function to parse content
|
310 |
+
if($.isFunction(content)) {
|
311 |
+
content = content.call(target, cache.event, self);
|
312 |
+
}
|
313 |
+
|
314 |
+
// Remove title if callback returns false or null/undefined (but not '')
|
315 |
+
if(content === FALSE || (!content && content !== '')) { return removeTitle(FALSE); }
|
316 |
+
|
317 |
+
// Append new content if its a DOM array and show it if hidden
|
318 |
+
else if(content.jquery && content.length > 0) {
|
319 |
+
elem.empty().append(content.css({ display: 'block' }));
|
320 |
+
}
|
321 |
+
|
322 |
+
// Content is a regular string, insert the new content
|
323 |
+
else { elem.html(content); }
|
324 |
+
|
325 |
+
// Redraw and reposition
|
326 |
+
self.redraw();
|
327 |
+
if(reposition !== FALSE && self.rendered && tooltip[0].offsetWidth > 0) {
|
328 |
+
self.reposition(cache.event);
|
329 |
+
}
|
330 |
+
}
|
331 |
+
|
332 |
+
function updateContent(content, reposition)
|
333 |
+
{
|
334 |
+
var elem = elements.content;
|
335 |
+
|
336 |
+
// Make sure tooltip is rendered and content is defined. If not return
|
337 |
+
if(!self.rendered || !content) { return FALSE; }
|
338 |
+
|
339 |
+
// Use function to parse content
|
340 |
+
if($.isFunction(content)) {
|
341 |
+
content = content.call(target, cache.event, self) || '';
|
342 |
+
}
|
343 |
+
|
344 |
+
// Append new content if its a DOM array and show it if hidden
|
345 |
+
if(content.jquery && content.length > 0) {
|
346 |
+
elem.empty().append(content.css({ display: 'block' }));
|
347 |
+
}
|
348 |
+
|
349 |
+
// Content is a regular string, insert the new content
|
350 |
+
else { elem.html(content); }
|
351 |
+
|
352 |
+
// Image detection
|
353 |
+
function detectImages(next) {
|
354 |
+
var images, srcs = {};
|
355 |
+
|
356 |
+
function imageLoad(image) {
|
357 |
+
// Clear src from object and any timers and events associated with the image
|
358 |
+
if(image) {
|
359 |
+
delete srcs[image.src];
|
360 |
+
clearTimeout(self.timers.img[image.src]);
|
361 |
+
$(image).unbind(namespace);
|
362 |
+
}
|
363 |
+
|
364 |
+
// If queue is empty after image removal, update tooltip and continue the queue
|
365 |
+
if($.isEmptyObject(srcs)) {
|
366 |
+
self.redraw();
|
367 |
+
if(reposition !== FALSE) {
|
368 |
+
self.reposition(cache.event);
|
369 |
+
}
|
370 |
+
|
371 |
+
next();
|
372 |
+
}
|
373 |
+
}
|
374 |
+
|
375 |
+
// Find all content images without dimensions, and if no images were found, continue
|
376 |
+
if((images = elem.find('img[src]:not([height]):not([width])')).length === 0) { return imageLoad(); }
|
377 |
+
|
378 |
+
// Apply timer to each image to poll for dimensions
|
379 |
+
images.each(function(i, elem) {
|
380 |
+
// Skip if the src is already present
|
381 |
+
if(srcs[elem.src] !== undefined) { return; }
|
382 |
+
|
383 |
+
// Keep track of how many times we poll for image dimensions.
|
384 |
+
// If it doesn't return in a reasonable amount of time, it's better
|
385 |
+
// to display the tooltip, rather than hold up the queue.
|
386 |
+
var iterations = 0, maxIterations = 3;
|
387 |
+
|
388 |
+
(function timer(){
|
389 |
+
// When the dimensions are found, remove the image from the queue
|
390 |
+
if(elem.height || elem.width || (iterations > maxIterations)) { return imageLoad(elem); }
|
391 |
+
|
392 |
+
// Increase iterations and restart timer
|
393 |
+
iterations += 1;
|
394 |
+
self.timers.img[elem.src] = setTimeout(timer, 700);
|
395 |
+
}());
|
396 |
+
|
397 |
+
// Also apply regular load/error event handlers
|
398 |
+
$(elem).bind('error'+namespace+' load'+namespace, function(){ imageLoad(this); });
|
399 |
+
|
400 |
+
// Store the src and element in our object
|
401 |
+
srcs[elem.src] = elem;
|
402 |
+
});
|
403 |
+
}
|
404 |
+
|
405 |
+
/*
|
406 |
+
* If we're still rendering... insert into 'fx' queue our image dimension
|
407 |
+
* checker which will halt the showing of the tooltip until image dimensions
|
408 |
+
* can be detected properly.
|
409 |
+
*/
|
410 |
+
if(self.rendered < 0) { tooltip.queue('fx', detectImages); }
|
411 |
+
|
412 |
+
// We're fully rendered, so reset isDrawing flag and proceed without queue delay
|
413 |
+
else { isDrawing = 0; detectImages($.noop); }
|
414 |
+
|
415 |
+
return self;
|
416 |
+
}
|
417 |
+
|
418 |
+
function assignEvents()
|
419 |
+
{
|
420 |
+
var posOptions = options.position,
|
421 |
+
targets = {
|
422 |
+
show: options.show.target,
|
423 |
+
hide: options.hide.target,
|
424 |
+
viewport: $(posOptions.viewport),
|
425 |
+
document: $(document),
|
426 |
+
body: $(document.body),
|
427 |
+
window: $(window)
|
428 |
+
},
|
429 |
+
events = {
|
430 |
+
show: $.trim('' + options.show.event).split(' '),
|
431 |
+
hide: $.trim('' + options.hide.event).split(' ')
|
432 |
+
},
|
433 |
+
IE6 = $.browser.msie && parseInt($.browser.version, 10) === 6;
|
434 |
+
|
435 |
+
// Define show event method
|
436 |
+
function showMethod(event)
|
437 |
+
{
|
438 |
+
if(tooltip.hasClass(disabled)) { return FALSE; }
|
439 |
+
|
440 |
+
// Clear hide timers
|
441 |
+
clearTimeout(self.timers.show);
|
442 |
+
clearTimeout(self.timers.hide);
|
443 |
+
|
444 |
+
// Start show timer
|
445 |
+
var callback = function(){ self.toggle(TRUE, event); };
|
446 |
+
if(options.show.delay > 0) {
|
447 |
+
self.timers.show = setTimeout(callback, options.show.delay);
|
448 |
+
}
|
449 |
+
else{ callback(); }
|
450 |
+
}
|
451 |
+
|
452 |
+
// Define hide method
|
453 |
+
function hideMethod(event)
|
454 |
+
{
|
455 |
+
if(tooltip.hasClass(disabled) || isPositioning || isDrawing) { return FALSE; }
|
456 |
+
|
457 |
+
// Check if new target was actually the tooltip element
|
458 |
+
var relatedTarget = $(event.relatedTarget || event.target),
|
459 |
+
ontoTooltip = relatedTarget.closest(selector)[0] === tooltip[0],
|
460 |
+
ontoTarget = relatedTarget[0] === targets.show[0];
|
461 |
+
|
462 |
+
// Clear timers and stop animation queue
|
463 |
+
clearTimeout(self.timers.show);
|
464 |
+
clearTimeout(self.timers.hide);
|
465 |
+
|
466 |
+
// Prevent hiding if tooltip is fixed and event target is the tooltip. Or if mouse positioning is enabled and cursor momentarily overlaps
|
467 |
+
if((posOptions.target === 'mouse' && ontoTooltip) || (options.hide.fixed && ((/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)))) {
|
468 |
+
try { event.preventDefault(); event.stopImmediatePropagation(); } catch(e) {} return;
|
469 |
+
}
|
470 |
+
|
471 |
+
// If tooltip has displayed, start hide timer
|
472 |
+
if(options.hide.delay > 0) {
|
473 |
+
self.timers.hide = setTimeout(function(){ self.hide(event); }, options.hide.delay);
|
474 |
+
}
|
475 |
+
else{ self.hide(event); }
|
476 |
+
}
|
477 |
+
|
478 |
+
// Define inactive method
|
479 |
+
function inactiveMethod(event)
|
480 |
+
{
|
481 |
+
if(tooltip.hasClass(disabled)) { return FALSE; }
|
482 |
+
|
483 |
+
// Clear timer
|
484 |
+
clearTimeout(self.timers.inactive);
|
485 |
+
self.timers.inactive = setTimeout(function(){ self.hide(event); }, options.hide.inactive);
|
486 |
+
}
|
487 |
+
|
488 |
+
function repositionMethod(event) {
|
489 |
+
if(self.rendered && tooltip[0].offsetWidth > 0) { self.reposition(event); }
|
490 |
+
}
|
491 |
+
|
492 |
+
// On mouseenter/mouseleave...
|
493 |
+
tooltip.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
|
494 |
+
var state = event.type === 'mouseenter';
|
495 |
+
|
496 |
+
// Focus the tooltip on mouseenter (z-index stacking)
|
497 |
+
if(state) { self.focus(event); }
|
498 |
+
|
499 |
+
// Add hover class
|
500 |
+
tooltip.toggleClass(hoverClass, state);
|
501 |
+
});
|
502 |
+
|
503 |
+
// If using mouseout/mouseleave as a hide event...
|
504 |
+
if(/mouse(out|leave)/i.test(options.hide.event)) {
|
505 |
+
// Hide tooltips when leaving current window/frame (but not select/option elements)
|
506 |
+
if(options.hide.leave === 'window') {
|
507 |
+
targets.window.bind('mouseout'+namespace+' blur'+namespace, function(event) {
|
508 |
+
if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) { self.hide(event); }
|
509 |
+
});
|
510 |
+
}
|
511 |
+
}
|
512 |
+
|
513 |
+
// Enable hide.fixed
|
514 |
+
if(options.hide.fixed) {
|
515 |
+
// Add tooltip as a hide target
|
516 |
+
targets.hide = targets.hide.add(tooltip);
|
517 |
+
|
518 |
+
// Clear hide timer on tooltip hover to prevent it from closing
|
519 |
+
tooltip.bind('mouseover'+namespace, function() {
|
520 |
+
if(!tooltip.hasClass(disabled)) { clearTimeout(self.timers.hide); }
|
521 |
+
});
|
522 |
+
}
|
523 |
+
|
524 |
+
/*
|
525 |
+
* Make sure hoverIntent functions properly by using mouseleave to clear show timer if
|
526 |
+
* mouseenter/mouseout is used for show.event, even if it isn't in the users options.
|
527 |
+
*/
|
528 |
+
else if(/mouse(over|enter)/i.test(options.show.event)) {
|
529 |
+
targets.hide.bind('mouseleave'+namespace, function(event) {
|
530 |
+
clearTimeout(self.timers.show);
|
531 |
+
});
|
532 |
+
}
|
533 |
+
|
534 |
+
// Hide tooltip on document mousedown if unfocus events are enabled
|
535 |
+
if(('' + options.hide.event).indexOf('unfocus') > -1) {
|
536 |
+
posOptions.container.closest('html').bind('mousedown'+namespace, function(event) {
|
537 |
+
var elem = $(event.target),
|
538 |
+
enabled = self.rendered && !tooltip.hasClass(disabled) && tooltip[0].offsetWidth > 0,
|
539 |
+
isAncestor = elem.parents(selector).filter(tooltip[0]).length > 0;
|
540 |
+
|
541 |
+
if(elem[0] !== target[0] && elem[0] !== tooltip[0] && !isAncestor &&
|
542 |
+
!target.has(elem[0]).length && !elem.attr('disabled')
|
543 |
+
) {
|
544 |
+
self.hide(event);
|
545 |
+
}
|
546 |
+
});
|
547 |
+
}
|
548 |
+
|
549 |
+
// Check if the tooltip hides when inactive
|
550 |
+
if('number' === typeof options.hide.inactive) {
|
551 |
+
// Bind inactive method to target as a custom event
|
552 |
+
targets.show.bind('qtip-'+id+'-inactive', inactiveMethod);
|
553 |
+
|
554 |
+
// Define events which reset the 'inactive' event handler
|
555 |
+
$.each(QTIP.inactiveEvents, function(index, type){
|
556 |
+
targets.hide.add(elements.tooltip).bind(type+namespace+'-inactive', inactiveMethod);
|
557 |
+
});
|
558 |
+
}
|
559 |
+
|
560 |
+
// Apply hide events
|
561 |
+
$.each(events.hide, function(index, type) {
|
562 |
+
var showIndex = $.inArray(type, events.show),
|
563 |
+
targetHide = $(targets.hide);
|
564 |
+
|
565 |
+
// Both events and targets are identical, apply events using a toggle
|
566 |
+
if((showIndex > -1 && targetHide.add(targets.show).length === targetHide.length) || type === 'unfocus')
|
567 |
+
{
|
568 |
+
targets.show.bind(type+namespace, function(event) {
|
569 |
+
if(tooltip[0].offsetWidth > 0) { hideMethod(event); }
|
570 |
+
else { showMethod(event); }
|
571 |
+
});
|
572 |
+
|
573 |
+
// Don't bind the event again
|
574 |
+
delete events.show[ showIndex ];
|
575 |
+
}
|
576 |
+
|
577 |
+
// Events are not identical, bind normally
|
578 |
+
else { targets.hide.bind(type+namespace, hideMethod); }
|
579 |
+
});
|
580 |
+
|
581 |
+
// Apply show events
|
582 |
+
$.each(events.show, function(index, type) {
|
583 |
+
targets.show.bind(type+namespace, showMethod);
|
584 |
+
});
|
585 |
+
|
586 |
+
// Check if the tooltip hides when mouse is moved a certain distance
|
587 |
+
if('number' === typeof options.hide.distance) {
|
588 |
+
// Bind mousemove to target to detect distance difference
|
589 |
+
targets.show.add(tooltip).bind('mousemove'+namespace, function(event) {
|
590 |
+
var origin = cache.origin || {},
|
591 |
+
limit = options.hide.distance,
|
592 |
+
abs = Math.abs;
|
593 |
+
|
594 |
+
// Check if the movement has gone beyond the limit, and hide it if so
|
595 |
+
if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
|
596 |
+
self.hide(event);
|
597 |
+
}
|
598 |
+
});
|
599 |
+
}
|
600 |
+
|
601 |
+
// Mouse positioning events
|
602 |
+
if(posOptions.target === 'mouse') {
|
603 |
+
// Cache mousemove coords on show targets
|
604 |
+
targets.show.bind('mousemove'+namespace, function(event) {
|
605 |
+
MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
|
606 |
+
});
|
607 |
+
|
608 |
+
// If mouse adjustment is on...
|
609 |
+
if(posOptions.adjust.mouse) {
|
610 |
+
// Apply a mouseleave event so we don't get problems with overlapping
|
611 |
+
if(options.hide.event) {
|
612 |
+
// Hide when we leave the tooltip and not onto the show target
|
613 |
+
tooltip.bind('mouseleave'+namespace, function(event) {
|
614 |
+
if((event.relatedTarget || event.target) !== targets.show[0]) { self.hide(event); }
|
615 |
+
});
|
616 |
+
|
617 |
+
// Track if we're on the target or not
|
618 |
+
elements.target.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
|
619 |
+
cache.onTarget = event.type === 'mouseenter';
|
620 |
+
});
|
621 |
+
}
|
622 |
+
|
623 |
+
// Update tooltip position on mousemove
|
624 |
+
targets.document.bind('mousemove'+namespace, function(event) {
|
625 |
+
// Update the tooltip position only if the tooltip is visible and adjustment is enabled
|
626 |
+
if(self.rendered && cache.onTarget && !tooltip.hasClass(disabled) && tooltip[0].offsetWidth > 0) {
|
627 |
+
self.reposition(event || MOUSE);
|
628 |
+
}
|
629 |
+
});
|
630 |
+
}
|
631 |
+
}
|
632 |
+
|
633 |
+
// Adjust positions of the tooltip on window resize if enabled
|
634 |
+
if(posOptions.adjust.resize || targets.viewport.length) {
|
635 |
+
($.event.special.resize ? targets.viewport : targets.window).bind('resize'+namespace, repositionMethod);
|
636 |
+
}
|
637 |
+
|
638 |
+
// Adjust tooltip position on scroll if screen adjustment is enabled
|
639 |
+
if(targets.viewport.length || (IE6 && tooltip.css('position') === 'fixed')) {
|
640 |
+
targets.viewport.bind('scroll'+namespace, repositionMethod);
|
641 |
+
}
|
642 |
+
}
|
643 |
+
|
644 |
+
function unassignEvents()
|
645 |
+
{
|
646 |
+
var targets = [
|
647 |
+
options.show.target[0],
|
648 |
+
options.hide.target[0],
|
649 |
+
self.rendered && elements.tooltip[0],
|
650 |
+
options.position.container[0],
|
651 |
+
options.position.viewport[0],
|
652 |
+
options.position.container.closest('html')[0], // unfocus
|
653 |
+
window,
|
654 |
+
document
|
655 |
+
];
|
656 |
+
|
657 |
+
// Check if tooltip is rendered
|
658 |
+
if(self.rendered) {
|
659 |
+
$([]).pushStack( $.grep(targets, function(i){ return typeof i === 'object'; }) ).unbind(namespace);
|
660 |
+
}
|
661 |
+
|
662 |
+
// Tooltip isn't yet rendered, remove render event
|
663 |
+
else { options.show.target.unbind(namespace+'-create'); }
|
664 |
+
}
|
665 |
+
|
666 |
+
// Setup builtin .set() option checks
|
667 |
+
self.checks.builtin = {
|
668 |
+
// Core checks
|
669 |
+
'^id$': function(obj, o, v) {
|
670 |
+
var id = v === TRUE ? QTIP.nextid : v,
|
671 |
+
tooltipID = uitooltip + '-' + id;
|
672 |
+
|
673 |
+
if(id !== FALSE && id.length > 0 && !$('#'+tooltipID).length) {
|
674 |
+
tooltip[0].id = tooltipID;
|
675 |
+
elements.content[0].id = tooltipID + '-content';
|
676 |
+
elements.title[0].id = tooltipID + '-title';
|
677 |
+
}
|
678 |
+
},
|
679 |
+
|
680 |
+
// Content checks
|
681 |
+
'^content.text$': function(obj, o, v){ updateContent(v); },
|
682 |
+
'^content.title.text$': function(obj, o, v) {
|
683 |
+
// Remove title if content is null
|
684 |
+
if(!v) { return removeTitle(); }
|
685 |
+
|
686 |
+
// If title isn't already created, create it now and update
|
687 |
+
if(!elements.title && v) { createTitle(); }
|
688 |
+
updateTitle(v);
|
689 |
+
},
|
690 |
+
'^content.title.button$': function(obj, o, v){ updateButton(v); },
|
691 |
+
|
692 |
+
// Position checks
|
693 |
+
'^position.(my|at)$': function(obj, o, v){
|
694 |
+
// Parse new corner value into Corner objecct
|
695 |
+
if('string' === typeof v) {
|
696 |
+
obj[o] = new PLUGINS.Corner(v);
|
697 |
+
}
|
698 |
+
},
|
699 |
+
'^position.container$': function(obj, o, v){
|
700 |
+
if(self.rendered) { tooltip.appendTo(v); }
|
701 |
+
},
|
702 |
+
|
703 |
+
// Show checks
|
704 |
+
'^show.ready$': function() {
|
705 |
+
if(!self.rendered) { self.render(1); }
|
706 |
+
else { self.toggle(TRUE); }
|
707 |
+
},
|
708 |
+
|
709 |
+
// Style checks
|
710 |
+
'^style.classes$': function(obj, o, v) {
|
711 |
+
tooltip.attr('class', uitooltip + ' qtip ' + v);
|
712 |
+
},
|
713 |
+
'^style.widget|content.title': setWidget,
|
714 |
+
|
715 |
+
// Events check
|
716 |
+
'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
|
717 |
+
tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
|
718 |
+
},
|
719 |
+
|
720 |
+
// Properties which require event reassignment
|
721 |
+
'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
|
722 |
+
var posOptions = options.position;
|
723 |
+
|
724 |
+
// Set tracking flag
|
725 |
+
tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
|
726 |
+
|
727 |
+
// Reassign events
|
728 |
+
unassignEvents(); assignEvents();
|
729 |
+
}
|
730 |
+
};
|
731 |
+
|
732 |
+
/*
|
733 |
+
* Public API methods
|
734 |
+
*/
|
735 |
+
$.extend(self, {
|
736 |
+
render: function(show)
|
737 |
+
{
|
738 |
+
if(self.rendered) { return self; } // If tooltip has already been rendered, exit
|
739 |
+
|
740 |
+
var text = options.content.text,
|
741 |
+
title = options.content.title.text,
|
742 |
+
posOptions = options.position;
|
743 |
+
|
744 |
+
// Add ARIA attributes to target
|
745 |
+
$.attr(target[0], 'aria-describedby', tooltipID);
|
746 |
+
|
747 |
+
// Create tooltip element
|
748 |
+
tooltip = elements.tooltip = $('<div/>', {
|
749 |
+
'id': tooltipID,
|
750 |
+
'class': uitooltip + ' qtip ' + defaultClass + ' ' + options.style.classes + ' '+ uitooltip + '-pos-' + options.position.my.abbrev(),
|
751 |
+
'width': options.style.width || '',
|
752 |
+
'height': options.style.height || '',
|
753 |
+
'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
|
754 |
+
|
755 |
+
/* ARIA specific attributes */
|
756 |
+
'role': 'alert',
|
757 |
+
'aria-live': 'polite',
|
758 |
+
'aria-atomic': FALSE,
|
759 |
+
'aria-describedby': tooltipID + '-content',
|
760 |
+
'aria-hidden': TRUE
|
761 |
+
})
|
762 |
+
.toggleClass(disabled, cache.disabled)
|
763 |
+
.data('qtip', self)
|
764 |
+
.appendTo(options.position.container)
|
765 |
+
.append(
|
766 |
+
// Create content element
|
767 |
+
elements.content = $('<div />', {
|
768 |
+
'class': uitooltip + '-content',
|
769 |
+
'id': tooltipID + '-content',
|
770 |
+
'aria-atomic': TRUE
|
771 |
+
})
|
772 |
+
);
|
773 |
+
|
774 |
+
// Set rendered flag and prevent redundant redraw/reposition calls for now
|
775 |
+
self.rendered = -1;
|
776 |
+
isDrawing = 1; isPositioning = 1;
|
777 |
+
|
778 |
+
// Create title...
|
779 |
+
if(title) {
|
780 |
+
createTitle();
|
781 |
+
|
782 |
+
// Update title only if its not a callback (called in toggle if so)
|
783 |
+
if(!$.isFunction(title)) { updateTitle(title, FALSE); }
|
784 |
+
}
|
785 |
+
|
786 |
+
// Set proper rendered flag and update content if not a callback function (called in toggle)
|
787 |
+
if(!$.isFunction(text)) { updateContent(text, FALSE); }
|
788 |
+
self.rendered = TRUE;
|
789 |
+
|
790 |
+
// Setup widget classes
|
791 |
+
setWidget();
|
792 |
+
|
793 |
+
// Assign passed event callbacks (before plugins!)
|
794 |
+
$.each(options.events, function(name, callback) {
|
795 |
+
if($.isFunction(callback)) {
|
796 |
+
tooltip.bind(name === 'toggle' ? 'tooltipshow tooltiphide' : 'tooltip'+name, callback);
|
797 |
+
}
|
798 |
+
});
|
799 |
+
|
800 |
+
// Initialize 'render' plugins
|
801 |
+
$.each(PLUGINS, function() {
|
802 |
+
if(this.initialize === 'render') { this(self); }
|
803 |
+
});
|
804 |
+
|
805 |
+
// Assign events
|
806 |
+
assignEvents();
|
807 |
+
|
808 |
+
/* Queue this part of the render process in our fx queue so we can
|
809 |
+
* load images before the tooltip renders fully.
|
810 |
+
*
|
811 |
+
* See: updateContent method
|
812 |
+
*/
|
813 |
+
tooltip.queue('fx', function(next) {
|
814 |
+
// tooltiprender event
|
815 |
+
triggerEvent('render');
|
816 |
+
|
817 |
+
// Reset flags
|
818 |
+
isDrawing = 0; isPositioning = 0;
|
819 |
+
|
820 |
+
// Redraw the tooltip manually now we're fully rendered
|
821 |
+
self.redraw();
|
822 |
+
|
823 |
+
// Show tooltip if needed
|
824 |
+
if(options.show.ready || show) {
|
825 |
+
self.toggle(TRUE, cache.event, FALSE);
|
826 |
+
}
|
827 |
+
|
828 |
+
next(); // Move on to next method in queue
|
829 |
+
});
|
830 |
+
|
831 |
+
return self;
|
832 |
+
},
|
833 |
+
|
834 |
+
get: function(notation)
|
835 |
+
{
|
836 |
+
var result, o;
|
837 |
+
|
838 |
+
switch(notation.toLowerCase())
|
839 |
+
{
|
840 |
+
case 'dimensions':
|
841 |
+
result = {
|
842 |
+
height: tooltip.outerHeight(), width: tooltip.outerWidth()
|
843 |
+
};
|
844 |
+
break;
|
845 |
+
|
846 |
+
case 'offset':
|
847 |
+
result = PLUGINS.offset(tooltip, options.position.container);
|
848 |
+
break;
|
849 |
+
|
850 |
+
default:
|
851 |
+
o = convertNotation(notation.toLowerCase());
|
852 |
+
result = o[0][ o[1] ];
|
853 |
+
result = result.precedance ? result.string() : result;
|
854 |
+
break;
|
855 |
+
}
|
856 |
+
|
857 |
+
return result;
|
858 |
+
},
|
859 |
+
|
860 |
+
set: function(option, value)
|
861 |
+
{
|
862 |
+
var rmove = /^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,
|
863 |
+
rdraw = /^content\.(title|attr)|style/i,
|
864 |
+
reposition = FALSE,
|
865 |
+
redraw = FALSE,
|
866 |
+
checks = self.checks,
|
867 |
+
name;
|
868 |
+
|
869 |
+
function callback(notation, args) {
|
870 |
+
var category, rule, match;
|
871 |
+
|
872 |
+
for(category in checks) {
|
873 |
+
for(rule in checks[category]) {
|
874 |
+
if(match = (new RegExp(rule, 'i')).exec(notation)) {
|
875 |
+
args.push(match);
|
876 |
+
checks[category][rule].apply(self, args);
|
877 |
+
}
|
878 |
+
}
|
879 |
+
}
|
880 |
+
}
|
881 |
+
|
882 |
+
// Convert singular option/value pair into object form
|
883 |
+
if('string' === typeof option) {
|
884 |
+
name = option; option = {}; option[name] = value;
|
885 |
+
}
|
886 |
+
else { option = $.extend(TRUE, {}, option); }
|
887 |
+
|
888 |
+
// Set all of the defined options to their new values
|
889 |
+
$.each(option, function(notation, value) {
|
890 |
+
var obj = convertNotation( notation.toLowerCase() ), previous;
|
891 |
+
|
892 |
+
// Set new obj value
|
893 |
+
previous = obj[0][ obj[1] ];
|
894 |
+
obj[0][ obj[1] ] = 'object' === typeof value && value.nodeType ? $(value) : value;
|
895 |
+
|
896 |
+
// Set the new params for the callback
|
897 |
+
option[notation] = [obj[0], obj[1], value, previous];
|
898 |
+
|
899 |
+
// Also check if we need to reposition / redraw
|
900 |
+
reposition = rmove.test(notation) || reposition;
|
901 |
+
redraw = rdraw.test(notation) || redraw;
|
902 |
+
});
|
903 |
+
|
904 |
+
// Re-sanitize options
|
905 |
+
sanitizeOptions(options);
|
906 |
+
|
907 |
+
/*
|
908 |
+
* Execute any valid callbacks for the set options
|
909 |
+
* Also set isPositioning/isDrawing so we don't get loads of redundant repositioning
|
910 |
+
* and redraw calls.
|
911 |
+
*/
|
912 |
+
isPositioning = isDrawing = 1; $.each(option, callback); isPositioning = isDrawing = 0;
|
913 |
+
|
914 |
+
// Update position / redraw if needed
|
915 |
+
if(self.rendered && tooltip[0].offsetWidth > 0) {
|
916 |
+
if(reposition) {
|
917 |
+
self.reposition( options.position.target === 'mouse' ? NULL : cache.event );
|
918 |
+
}
|
919 |
+
if(redraw) { self.redraw(); }
|
920 |
+
}
|
921 |
+
|
922 |
+
return self;
|
923 |
+
},
|
924 |
+
|
925 |
+
toggle: function(state, event)
|
926 |
+
{
|
927 |
+
// Render the tooltip if showing and it isn't already
|
928 |
+
if(!self.rendered) { return state ? self.render(1) : self; }
|
929 |
+
|
930 |
+
var type = state ? 'show' : 'hide',
|
931 |
+
opts = options[type],
|
932 |
+
otherOpts = options[ !state ? 'show' : 'hide' ],
|
933 |
+
posOptions = options.position,
|
934 |
+
contentOptions = options.content,
|
935 |
+
visible = tooltip[0].offsetWidth > 0,
|
936 |
+
animate = state || opts.target.length === 1,
|
937 |
+
sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
|
938 |
+
showEvent, delay;
|
939 |
+
|
940 |
+
// Detect state if valid one isn't provided
|
941 |
+
if((typeof state).search('boolean|number')) { state = !visible; }
|
942 |
+
|
943 |
+
// Return if element is already in correct state
|
944 |
+
if(!tooltip.is(':animated') && visible === state && sameTarget) { return self; }
|
945 |
+
|
946 |
+
// Try to prevent flickering when tooltip overlaps show element
|
947 |
+
if(event) {
|
948 |
+
if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&
|
949 |
+
options.show.target.add(event.target).length === options.show.target.length &&
|
950 |
+
tooltip.has(event.relatedTarget).length) {
|
951 |
+
return self;
|
952 |
+
}
|
953 |
+
|
954 |
+
// Cache event
|
955 |
+
cache.event = $.extend({}, event);
|
956 |
+
}
|
957 |
+
|
958 |
+
// tooltipshow/tooltiphide events
|
959 |
+
if(!triggerEvent(type, [90])) { return self; }
|
960 |
+
|
961 |
+
// Set ARIA hidden status attribute
|
962 |
+
$.attr(tooltip[0], 'aria-hidden', !!!state);
|
963 |
+
|
964 |
+
// Execute state specific properties
|
965 |
+
if(state) {
|
966 |
+
// Store show origin coordinates
|
967 |
+
cache.origin = $.extend({}, MOUSE);
|
968 |
+
|
969 |
+
// Focus the tooltip
|
970 |
+
self.focus(event);
|
971 |
+
|
972 |
+
// Update tooltip content & title if it's a dynamic function
|
973 |
+
if($.isFunction(contentOptions.text)) { updateContent(contentOptions.text, FALSE); }
|
974 |
+
if($.isFunction(contentOptions.title.text)) { updateTitle(contentOptions.title.text, FALSE); }
|
975 |
+
|
976 |
+
// Cache mousemove events for positioning purposes (if not already tracking)
|
977 |
+
if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
|
978 |
+
$(document).bind('mousemove.qtip', function(event) {
|
979 |
+
MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
|
980 |
+
});
|
981 |
+
trackingBound = TRUE;
|
982 |
+
}
|
983 |
+
|
984 |
+
// Update the tooltip position
|
985 |
+
self.reposition(event, arguments[2]);
|
986 |
+
|
987 |
+
// Hide other tooltips if tooltip is solo
|
988 |
+
if(!!opts.solo) {
|
989 |
+
$(selector, opts.solo).not(tooltip).qtip('hide', $.Event('tooltipsolo'));
|
990 |
+
}
|
991 |
+
}
|
992 |
+
else {
|
993 |
+
// Clear show timer if we're hiding
|
994 |
+
clearTimeout(self.timers.show);
|
995 |
+
|
996 |
+
// Remove cached origin on hide
|
997 |
+
delete cache.origin;
|
998 |
+
|
999 |
+
// Remove mouse tracking event if not needed (all tracking qTips are hidden)
|
1000 |
+
if(trackingBound && !$(selector+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
|
1001 |
+
$(document).unbind('mousemove.qtip');
|
1002 |
+
trackingBound = FALSE;
|
1003 |
+
}
|
1004 |
+
|
1005 |
+
// Blur the tooltip
|
1006 |
+
self.blur(event);
|
1007 |
+
}
|
1008 |
+
|
1009 |
+
// Define post-animation, state specific properties
|
1010 |
+
function after() {
|
1011 |
+
if(state) {
|
1012 |
+
// Prevent antialias from disappearing in IE by removing filter
|
1013 |
+
if($.browser.msie) { tooltip[0].style.removeAttribute('filter'); }
|
1014 |
+
|
1015 |
+
// Remove overflow setting to prevent tip bugs
|
1016 |
+
tooltip.css('overflow', '');
|
1017 |
+
|
1018 |
+
// Autofocus elements if enabled
|
1019 |
+
if('string' === typeof opts.autofocus) {
|
1020 |
+
$(opts.autofocus, tooltip).focus();
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
// If set, hide tooltip when inactive for delay period
|
1024 |
+
opts.target.trigger('qtip-'+id+'-inactive');
|
1025 |
+
}
|
1026 |
+
else {
|
1027 |
+
// Reset CSS states
|
1028 |
+
tooltip.css({
|
1029 |
+
display: '',
|
1030 |
+
visibility: '',
|
1031 |
+
opacity: '',
|
1032 |
+
left: '',
|
1033 |
+
top: ''
|
1034 |
+
});
|
1035 |
+
}
|
1036 |
+
|
1037 |
+
// tooltipvisible/tooltiphidden events
|
1038 |
+
triggerEvent(state ? 'visible' : 'hidden');
|
1039 |
+
}
|
1040 |
+
|
1041 |
+
// If no effect type is supplied, use a simple toggle
|
1042 |
+
if(opts.effect === FALSE || animate === FALSE) {
|
1043 |
+
tooltip[ type ]();
|
1044 |
+
after.call(tooltip);
|
1045 |
+
}
|
1046 |
+
|
1047 |
+
// Use custom function if provided
|
1048 |
+
else if($.isFunction(opts.effect)) {
|
1049 |
+
tooltip.stop(1, 1);
|
1050 |
+
opts.effect.call(tooltip, self);
|
1051 |
+
tooltip.queue('fx', function(n){ after(); n(); });
|
1052 |
+
}
|
1053 |
+
|
1054 |
+
// Use basic fade function by default
|
1055 |
+
else { tooltip.fadeTo(90, state ? 1 : 0, after); }
|
1056 |
+
|
1057 |
+
// If inactive hide method is set, active it
|
1058 |
+
if(state) { opts.target.trigger('qtip-'+id+'-inactive'); }
|
1059 |
+
|
1060 |
+
return self;
|
1061 |
+
},
|
1062 |
+
|
1063 |
+
show: function(event){ return self.toggle(TRUE, event); },
|
1064 |
+
|
1065 |
+
hide: function(event){ return self.toggle(FALSE, event); },
|
1066 |
+
|
1067 |
+
focus: function(event)
|
1068 |
+
{
|
1069 |
+
if(!self.rendered) { return self; }
|
1070 |
+
|
1071 |
+
var qtips = $(selector),
|
1072 |
+
curIndex = parseInt(tooltip[0].style.zIndex, 10),
|
1073 |
+
newIndex = QTIP.zindex + qtips.length,
|
1074 |
+
cachedEvent = $.extend({}, event),
|
1075 |
+
focusedElem;
|
1076 |
+
|
1077 |
+
// Only update the z-index if it has changed and tooltip is not already focused
|
1078 |
+
if(!tooltip.hasClass(focusClass))
|
1079 |
+
{
|
1080 |
+
// tooltipfocus event
|
1081 |
+
if(triggerEvent('focus', [newIndex], cachedEvent)) {
|
1082 |
+
// Only update z-index's if they've changed
|
1083 |
+
if(curIndex !== newIndex) {
|
1084 |
+
// Reduce our z-index's and keep them properly ordered
|
1085 |
+
qtips.each(function() {
|
1086 |
+
if(this.style.zIndex > curIndex) {
|
1087 |
+
this.style.zIndex = this.style.zIndex - 1;
|
1088 |
+
}
|
1089 |
+
});
|
1090 |
+
|
1091 |
+
// Fire blur event for focused tooltip
|
1092 |
+
qtips.filter('.' + focusClass).qtip('blur', cachedEvent);
|
1093 |
+
}
|
1094 |
+
|
1095 |
+
// Set the new z-index
|
1096 |
+
tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
|
1097 |
+
}
|
1098 |
+
}
|
1099 |
+
|
1100 |
+
return self;
|
1101 |
+
},
|
1102 |
+
|
1103 |
+
blur: function(event) {
|
1104 |
+
// Set focused status to FALSE
|
1105 |
+
tooltip.removeClass(focusClass);
|
1106 |
+
|
1107 |
+
// tooltipblur event
|
1108 |
+
triggerEvent('blur', [tooltip.css('zIndex')], event);
|
1109 |
+
|
1110 |
+
return self;
|
1111 |
+
},
|
1112 |
+
|
1113 |
+
reposition: function(event, effect)
|
1114 |
+
{
|
1115 |
+
if(!self.rendered || isPositioning) { return self; }
|
1116 |
+
|
1117 |
+
// Set positioning flag
|
1118 |
+
isPositioning = 1;
|
1119 |
+
|
1120 |
+
var target = options.position.target,
|
1121 |
+
posOptions = options.position,
|
1122 |
+
my = posOptions.my,
|
1123 |
+
at = posOptions.at,
|
1124 |
+
adjust = posOptions.adjust,
|
1125 |
+
method = adjust.method.split(' '),
|
1126 |
+
elemWidth = tooltip.outerWidth(),
|
1127 |
+
elemHeight = tooltip.outerHeight(),
|
1128 |
+
targetWidth = 0,
|
1129 |
+
targetHeight = 0,
|
1130 |
+
fixed = tooltip.css('position') === 'fixed',
|
1131 |
+
viewport = posOptions.viewport,
|
1132 |
+
position = { left: 0, top: 0 },
|
1133 |
+
container = posOptions.container,
|
1134 |
+
visible = tooltip[0].offsetWidth > 0,
|
1135 |
+
adjusted, offset, win;
|
1136 |
+
|
1137 |
+
// Check if absolute position was passed
|
1138 |
+
if($.isArray(target) && target.length === 2) {
|
1139 |
+
// Force left top and set position
|
1140 |
+
at = { x: LEFT, y: TOP };
|
1141 |
+
position = { left: target[0], top: target[1] };
|
1142 |
+
}
|
1143 |
+
|
1144 |
+
// Check if mouse was the target
|
1145 |
+
else if(target === 'mouse' && ((event && event.pageX) || cache.event.pageX)) {
|
1146 |
+
// Force left top to allow flipping
|
1147 |
+
at = { x: LEFT, y: TOP };
|
1148 |
+
|
1149 |
+
// Use cached event if one isn't available for positioning
|
1150 |
+
event = (event && (event.type === 'resize' || event.type === 'scroll') ? cache.event :
|
1151 |
+
event && event.pageX && event.type === 'mousemove' ? event :
|
1152 |
+
MOUSE && MOUSE.pageX && (adjust.mouse || !event || !event.pageX) ? { pageX: MOUSE.pageX, pageY: MOUSE.pageY } :
|
1153 |
+
!adjust.mouse && cache.origin && cache.origin.pageX && options.show.distance ? cache.origin :
|
1154 |
+
event) || event || cache.event || MOUSE || {};
|
1155 |
+
|
1156 |
+
// Use event coordinates for position
|
1157 |
+
position = { top: event.pageY, left: event.pageX };
|
1158 |
+
}
|
1159 |
+
|
1160 |
+
// Target wasn't mouse or absolute...
|
1161 |
+
else {
|
1162 |
+
// Check if event targetting is being used
|
1163 |
+
if(target === 'event' && event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
|
1164 |
+
cache.target = $(event.target);
|
1165 |
+
}
|
1166 |
+
else if(target !== 'event'){
|
1167 |
+
cache.target = $(target.jquery ? target : elements.target);
|
1168 |
+
}
|
1169 |
+
target = cache.target;
|
1170 |
+
|
1171 |
+
// Parse the target into a jQuery object and make sure there's an element present
|
1172 |
+
target = $(target).eq(0);
|
1173 |
+
if(target.length === 0) { return self; }
|
1174 |
+
|
1175 |
+
// Check if window or document is the target
|
1176 |
+
else if(target[0] === document || target[0] === window) {
|
1177 |
+
targetWidth = PLUGINS.iOS ? window.innerWidth : target.width();
|
1178 |
+
targetHeight = PLUGINS.iOS ? window.innerHeight : target.height();
|
1179 |
+
|
1180 |
+
if(target[0] === window) {
|
1181 |
+
position = {
|
1182 |
+
top: (viewport || target).scrollTop(),
|
1183 |
+
left: (viewport || target).scrollLeft()
|
1184 |
+
};
|
1185 |
+
}
|
1186 |
+
}
|
1187 |
+
|
1188 |
+
// Use Imagemap/SVG plugins if needed
|
1189 |
+
else if(PLUGINS.imagemap && target.is('area')) {
|
1190 |
+
adjusted = PLUGINS.imagemap(self, target, at, PLUGINS.viewport ? method : FALSE);
|
1191 |
+
}
|
1192 |
+
else if(PLUGINS.svg && typeof target[0].xmlbase === 'string') {
|
1193 |
+
adjusted = PLUGINS.svg(self, target, at, PLUGINS.viewport ? method : FALSE);
|
1194 |
+
}
|
1195 |
+
|
1196 |
+
else {
|
1197 |
+
targetWidth = target.outerWidth();
|
1198 |
+
targetHeight = target.outerHeight();
|
1199 |
+
|
1200 |
+
position = PLUGINS.offset(target, container);
|
1201 |
+
}
|
1202 |
+
|
1203 |
+
// Parse returned plugin values into proper variables
|
1204 |
+
if(adjusted) {
|
1205 |
+
targetWidth = adjusted.width;
|
1206 |
+
targetHeight = adjusted.height;
|
1207 |
+
offset = adjusted.offset;
|
1208 |
+
position = adjusted.position;
|
1209 |
+
}
|
1210 |
+
|
1211 |
+
// Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
|
1212 |
+
if((PLUGINS.iOS > 3.1 && PLUGINS.iOS < 4.1) ||
|
1213 |
+
(PLUGINS.iOS >= 4.3 && PLUGINS.iOS < 4.33) ||
|
1214 |
+
(!PLUGINS.iOS && fixed)
|
1215 |
+
){
|
1216 |
+
win = $(window);
|
1217 |
+
position.left -= win.scrollLeft();
|
1218 |
+
position.top -= win.scrollTop();
|
1219 |
+
}
|
1220 |
+
|
1221 |
+
// Adjust position relative to target
|
1222 |
+
position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
|
1223 |
+
position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
|
1224 |
+
}
|
1225 |
+
|
1226 |
+
// Adjust position relative to tooltip
|
1227 |
+
position.left += adjust.x + (my.x === RIGHT ? -elemWidth : my.x === CENTER ? -elemWidth / 2 : 0);
|
1228 |
+
position.top += adjust.y + (my.y === BOTTOM ? -elemHeight : my.y === CENTER ? -elemHeight / 2 : 0);
|
1229 |
+
|
1230 |
+
// Use viewport adjustment plugin if enabled
|
1231 |
+
if(PLUGINS.viewport) {
|
1232 |
+
position.adjusted = PLUGINS.viewport(
|
1233 |
+
self, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight
|
1234 |
+
);
|
1235 |
+
|
1236 |
+
// Apply offsets supplied by positioning plugin (if used)
|
1237 |
+
if(offset && position.adjusted.left) { position.left += offset.left; }
|
1238 |
+
if(offset && position.adjusted.top) { position.top += offset.top; }
|
1239 |
+
}
|
1240 |
+
|
1241 |
+
// Viewport adjustment is disabled, set values to zero
|
1242 |
+
else { position.adjusted = { left: 0, top: 0 }; }
|
1243 |
+
|
1244 |
+
// tooltipmove event
|
1245 |
+
if(!triggerEvent('move', [position, viewport.elem || viewport], event)) { return self; }
|
1246 |
+
delete position.adjusted;
|
1247 |
+
|
1248 |
+
// If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
|
1249 |
+
if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
|
1250 |
+
tooltip.css(position);
|
1251 |
+
}
|
1252 |
+
|
1253 |
+
// Use custom function if provided
|
1254 |
+
else if($.isFunction(posOptions.effect)) {
|
1255 |
+
posOptions.effect.call(tooltip, self, $.extend({}, position));
|
1256 |
+
tooltip.queue(function(next) {
|
1257 |
+
// Reset attributes to avoid cross-browser rendering bugs
|
1258 |
+
$(this).css({ opacity: '', height: '' });
|
1259 |
+
if($.browser.msie) { this.style.removeAttribute('filter'); }
|
1260 |
+
|
1261 |
+
next();
|
1262 |
+
});
|
1263 |
+
}
|
1264 |
+
|
1265 |
+
// Set positioning flag
|
1266 |
+
isPositioning = 0;
|
1267 |
+
|
1268 |
+
return self;
|
1269 |
+
},
|
1270 |
+
|
1271 |
+
// Max/min width simulator function for all browsers.. yeaaah!
|
1272 |
+
redraw: function()
|
1273 |
+
{
|
1274 |
+
if(self.rendered < 1 || isDrawing) { return self; }
|
1275 |
+
|
1276 |
+
var style = options.style,
|
1277 |
+
container = options.position.container,
|
1278 |
+
perc, width, max, min;
|
1279 |
+
|
1280 |
+
// Set drawing flag
|
1281 |
+
isDrawing = 1;
|
1282 |
+
|
1283 |
+
// tooltipredraw event
|
1284 |
+
triggerEvent('redraw');
|
1285 |
+
|
1286 |
+
// If tooltip has a set height/width, just set it... like a boss!
|
1287 |
+
if(style.height) { tooltip.css(HEIGHT, style.height); }
|
1288 |
+
if(style.width) { tooltip.css(WIDTH, style.width); }
|
1289 |
+
|
1290 |
+
// Simulate max/min width if not set width present...
|
1291 |
+
else {
|
1292 |
+
// Reset width and add fluid class
|
1293 |
+
tooltip.css(WIDTH, '').appendTo(redrawContainer);
|
1294 |
+
|
1295 |
+
// Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!)
|
1296 |
+
width = tooltip.width();
|
1297 |
+
if(width % 2 < 1) { width += 1; }
|
1298 |
+
|
1299 |
+
// Grab our max/min properties
|
1300 |
+
max = tooltip.css('max-width') || '';
|
1301 |
+
min = tooltip.css('min-width') || '';
|
1302 |
+
|
1303 |
+
// Parse into proper pixel values
|
1304 |
+
perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
|
1305 |
+
max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
|
1306 |
+
min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
|
1307 |
+
|
1308 |
+
// Determine new dimension size based on max/min/current values
|
1309 |
+
width = max + min ? Math.min(Math.max(width, min), max) : width;
|
1310 |
+
|
1311 |
+
// Set the newly calculated width and remvoe fluid class
|
1312 |
+
tooltip.css(WIDTH, Math.round(width)).appendTo(container);
|
1313 |
+
}
|
1314 |
+
|
1315 |
+
// tooltipredrawn event
|
1316 |
+
triggerEvent('redrawn');
|
1317 |
+
|
1318 |
+
// Set drawing flag
|
1319 |
+
isDrawing = 0;
|
1320 |
+
|
1321 |
+
return self;
|
1322 |
+
},
|
1323 |
+
|
1324 |
+
disable: function(state)
|
1325 |
+
{
|
1326 |
+
if('boolean' !== typeof state) {
|
1327 |
+
state = !(tooltip.hasClass(disabled) || cache.disabled);
|
1328 |
+
}
|
1329 |
+
|
1330 |
+
if(self.rendered) {
|
1331 |
+
tooltip.toggleClass(disabled, state);
|
1332 |
+
$.attr(tooltip[0], 'aria-disabled', state);
|
1333 |
+
}
|
1334 |
+
else {
|
1335 |
+
cache.disabled = !!state;
|
1336 |
+
}
|
1337 |
+
|
1338 |
+
return self;
|
1339 |
+
},
|
1340 |
+
|
1341 |
+
enable: function() { return self.disable(FALSE); },
|
1342 |
+
|
1343 |
+
destroy: function()
|
1344 |
+
{
|
1345 |
+
var t = target[0],
|
1346 |
+
title = $.attr(t, oldtitle),
|
1347 |
+
elemAPI = target.data('qtip');
|
1348 |
+
|
1349 |
+
// Set flag the signify destroy is taking place to plugins
|
1350 |
+
self.destroyed = TRUE;
|
1351 |
+
|
1352 |
+
// Destroy tooltip and any associated plugins if rendered
|
1353 |
+
if(self.rendered) {
|
1354 |
+
tooltip.stop(1,0).remove();
|
1355 |
+
|
1356 |
+
$.each(self.plugins, function() {
|
1357 |
+
if(this.destroy) { this.destroy(); }
|
1358 |
+
});
|
1359 |
+
}
|
1360 |
+
|
1361 |
+
// Clear timers and remove bound events
|
1362 |
+
clearTimeout(self.timers.show);
|
1363 |
+
clearTimeout(self.timers.hide);
|
1364 |
+
unassignEvents();
|
1365 |
+
|
1366 |
+
// If the API if actually this qTip API...
|
1367 |
+
if(!elemAPI || self === elemAPI) {
|
1368 |
+
// Remove api object
|
1369 |
+
$.removeData(t, 'qtip');
|
1370 |
+
|
1371 |
+
// Reset old title attribute if removed
|
1372 |
+
if(options.suppress && title) {
|
1373 |
+
$.attr(t, 'title', title);
|
1374 |
+
target.removeAttr(oldtitle);
|
1375 |
+
}
|
1376 |
+
|
1377 |
+
// Remove ARIA attributes
|
1378 |
+
target.removeAttr('aria-describedby');
|
1379 |
+
}
|
1380 |
+
|
1381 |
+
// Remove qTip events associated with this API
|
1382 |
+
target.unbind('.qtip-'+id);
|
1383 |
+
|
1384 |
+
// Remove ID from sued id object
|
1385 |
+
delete usedIDs[self.id];
|
1386 |
+
|
1387 |
+
return target;
|
1388 |
+
}
|
1389 |
+
});
|
1390 |
+
}
|
1391 |
+
|
1392 |
+
// Initialization method
|
1393 |
+
function init(id, opts)
|
1394 |
+
{
|
1395 |
+
var obj, posOptions, attr, config, title,
|
1396 |
+
|
1397 |
+
// Setup element references
|
1398 |
+
elem = $(this),
|
1399 |
+
docBody = $(document.body),
|
1400 |
+
|
1401 |
+
// Use document body instead of document element if needed
|
1402 |
+
newTarget = this === document ? docBody : elem,
|
1403 |
+
|
1404 |
+
// Grab metadata from element if plugin is present
|
1405 |
+
metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
|
1406 |
+
|
1407 |
+
// If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
|
1408 |
+
metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
|
1409 |
+
|
1410 |
+
// Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
|
1411 |
+
html5 = elem.data(opts.metadata.name || 'qtipopts');
|
1412 |
+
|
1413 |
+
// If we don't get an object returned attempt to parse it manualyl without parseJSON
|
1414 |
+
try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } catch(e) {}
|
1415 |
+
|
1416 |
+
// Merge in and sanitize metadata
|
1417 |
+
config = $.extend(TRUE, {}, QTIP.defaults, opts,
|
1418 |
+
typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
|
1419 |
+
sanitizeOptions(metadata5 || metadata));
|
1420 |
+
|
1421 |
+
// Re-grab our positioning options now we've merged our metadata and set id to passed value
|
1422 |
+
posOptions = config.position;
|
1423 |
+
config.id = id;
|
1424 |
+
|
1425 |
+
// Setup missing content if none is detected
|
1426 |
+
if('boolean' === typeof config.content.text) {
|
1427 |
+
attr = elem.attr(config.content.attr);
|
1428 |
+
|
1429 |
+
// Grab from supplied attribute if available
|
1430 |
+
if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
|
1431 |
+
|
1432 |
+
// No valid content was found, abort render
|
1433 |
+
else { return FALSE; }
|
1434 |
+
}
|
1435 |
+
|
1436 |
+
// Setup target options
|
1437 |
+
if(!posOptions.container.length) { posOptions.container = docBody; }
|
1438 |
+
if(posOptions.target === FALSE) { posOptions.target = newTarget; }
|
1439 |
+
if(config.show.target === FALSE) { config.show.target = newTarget; }
|
1440 |
+
if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
|
1441 |
+
if(config.hide.target === FALSE) { config.hide.target = newTarget; }
|
1442 |
+
if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
|
1443 |
+
|
1444 |
+
// Ensure we only use a single container
|
1445 |
+
posOptions.container = posOptions.container.eq(0);
|
1446 |
+
|
1447 |
+
// Convert position corner values into x and y strings
|
1448 |
+
posOptions.at = new PLUGINS.Corner(posOptions.at);
|
1449 |
+
posOptions.my = new PLUGINS.Corner(posOptions.my);
|
1450 |
+
|
1451 |
+
// Destroy previous tooltip if overwrite is enabled, or skip element if not
|
1452 |
+
if($.data(this, 'qtip')) {
|
1453 |
+
if(config.overwrite) {
|
1454 |
+
elem.qtip('destroy');
|
1455 |
+
}
|
1456 |
+
else if(config.overwrite === FALSE) {
|
1457 |
+
return FALSE;
|
1458 |
+
}
|
1459 |
+
}
|
1460 |
+
|
1461 |
+
// Remove title attribute and store it if present
|
1462 |
+
if(config.suppress && (title = $.attr(this, 'title'))) {
|
1463 |
+
// Final attr call fixes event delegatiom and IE default tooltip showing problem
|
1464 |
+
$(this).removeAttr('title').attr(oldtitle, title).attr('title', '');
|
1465 |
+
}
|
1466 |
+
|
1467 |
+
// Initialize the tooltip and add API reference
|
1468 |
+
obj = new QTip(elem, config, id, !!attr);
|
1469 |
+
$.data(this, 'qtip', obj);
|
1470 |
+
|
1471 |
+
// Catch remove/removeqtip events on target element to destroy redundant tooltip
|
1472 |
+
elem.bind('remove.qtip-'+id+' removeqtip.qtip-'+id, function(){ obj.destroy(); });
|
1473 |
+
|
1474 |
+
return obj;
|
1475 |
+
}
|
1476 |
+
|
1477 |
+
// jQuery $.fn extension method
|
1478 |
+
QTIP = $.fn.qtip = function(options, notation, newValue)
|
1479 |
+
{
|
1480 |
+
var command = ('' + options).toLowerCase(), // Parse command
|
1481 |
+
returned = NULL,
|
1482 |
+
args = $.makeArray(arguments).slice(1),
|
1483 |
+
event = args[args.length - 1],
|
1484 |
+
opts = this[0] ? $.data(this[0], 'qtip') : NULL;
|
1485 |
+
|
1486 |
+
// Check for API request
|
1487 |
+
if((!arguments.length && opts) || command === 'api') {
|
1488 |
+
return opts;
|
1489 |
+
}
|
1490 |
+
|
1491 |
+
// Execute API command if present
|
1492 |
+
else if('string' === typeof options)
|
1493 |
+
{
|
1494 |
+
this.each(function()
|
1495 |
+
{
|
1496 |
+
var api = $.data(this, 'qtip');
|
1497 |
+
if(!api) { return TRUE; }
|
1498 |
+
|
1499 |
+
// Cache the event if possible
|
1500 |
+
if(event && event.timeStamp) { api.cache.event = event; }
|
1501 |
+
|
1502 |
+
// Check for specific API commands
|
1503 |
+
if((command === 'option' || command === 'options') && notation) {
|
1504 |
+
if($.isPlainObject(notation) || newValue !== undefined) {
|
1505 |
+
api.set(notation, newValue);
|
1506 |
+
}
|
1507 |
+
else {
|
1508 |
+
returned = api.get(notation);
|
1509 |
+
return FALSE;
|
1510 |
+
}
|
1511 |
+
}
|
1512 |
+
|
1513 |
+
// Execute API command
|
1514 |
+
else if(api[command]) {
|
1515 |
+
api[command].apply(api[command], args);
|
1516 |
+
}
|
1517 |
+
});
|
1518 |
+
|
1519 |
+
return returned !== NULL ? returned : this;
|
1520 |
+
}
|
1521 |
+
|
1522 |
+
// No API commands. validate provided options and setup qTips
|
1523 |
+
else if('object' === typeof options || !arguments.length)
|
1524 |
+
{
|
1525 |
+
opts = sanitizeOptions($.extend(TRUE, {}, options));
|
1526 |
+
|
1527 |
+
// Bind the qTips
|
1528 |
+
return QTIP.bind.call(this, opts, event);
|
1529 |
+
}
|
1530 |
+
};
|
1531 |
+
|
1532 |
+
// $.fn.qtip Bind method
|
1533 |
+
QTIP.bind = function(opts, event)
|
1534 |
+
{
|
1535 |
+
return this.each(function(i) {
|
1536 |
+
var options, targets, events, namespace, api, id;
|
1537 |
+
|
1538 |
+
// Find next available ID, or use custom ID if provided
|
1539 |
+
id = $.isArray(opts.id) ? opts.id[i] : opts.id;
|
1540 |
+
id = !id || id === FALSE || id.length < 1 || usedIDs[id] ? QTIP.nextid++ : (usedIDs[id] = id);
|
1541 |
+
|
1542 |
+
// Setup events namespace
|
1543 |
+
namespace = '.qtip-'+id+'-create';
|
1544 |
+
|
1545 |
+
// Initialize the qTip and re-grab newly sanitized options
|
1546 |
+
api = init.call(this, id, opts);
|
1547 |
+
if(api === FALSE) { return TRUE; }
|
1548 |
+
options = api.options;
|
1549 |
+
|
1550 |
+
// Initialize plugins
|
1551 |
+
$.each(PLUGINS, function() {
|
1552 |
+
if(this.initialize === 'initialize') { this(api); }
|
1553 |
+
});
|
1554 |
+
|
1555 |
+
// Determine hide and show targets
|
1556 |
+
targets = { show: options.show.target, hide: options.hide.target };
|
1557 |
+
events = {
|
1558 |
+
show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace,
|
1559 |
+
hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace
|
1560 |
+
};
|
1561 |
+
|
1562 |
+
/*
|
1563 |
+
* Make sure hoverIntent functions properly by using mouseleave as a hide event if
|
1564 |
+
* mouseenter/mouseout is used for show.event, even if it isn't in the users options.
|
1565 |
+
*/
|
1566 |
+
if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {
|
1567 |
+
events.hide += ' mouseleave' + namespace;
|
1568 |
+
}
|
1569 |
+
|
1570 |
+
/*
|
1571 |
+
* Also make sure initial mouse targetting works correctly by caching mousemove coords
|
1572 |
+
* on show targets before the tooltip has rendered.
|
1573 |
+
*
|
1574 |
+
* Also set onTarget when triggered to keep mouse tracking working
|
1575 |
+
*/
|
1576 |
+
targets.show.bind('mousemove'+namespace, function(event) {
|
1577 |
+
MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
|
1578 |
+
api.cache.onTarget = TRUE;
|
1579 |
+
});
|
1580 |
+
|
1581 |
+
// Define hoverIntent function
|
1582 |
+
function hoverIntent(event) {
|
1583 |
+
function render() {
|
1584 |
+
// Cache mouse coords,render and render the tooltip
|
1585 |
+
api.render(typeof event === 'object' || options.show.ready);
|
1586 |
+
|
1587 |
+
// Unbind show and hide events
|
1588 |
+
targets.show.add(targets.hide).unbind(namespace);
|
1589 |
+
}
|
1590 |
+
|
1591 |
+
// Only continue if tooltip isn't disabled
|
1592 |
+
if(api.cache.disabled) { return FALSE; }
|
1593 |
+
|
1594 |
+
// Cache the event data
|
1595 |
+
api.cache.event = $.extend({}, event);
|
1596 |
+
api.cache.target = event ? $(event.target) : [undefined];
|
1597 |
+
|
1598 |
+
// Start the event sequence
|
1599 |
+
if(options.show.delay > 0) {
|
1600 |
+
clearTimeout(api.timers.show);
|
1601 |
+
api.timers.show = setTimeout(render, options.show.delay);
|
1602 |
+
if(events.show !== events.hide) {
|
1603 |
+
targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); });
|
1604 |
+
}
|
1605 |
+
}
|
1606 |
+
else { render(); }
|
1607 |
+
}
|
1608 |
+
|
1609 |
+
// Bind show events to target
|
1610 |
+
targets.show.bind(events.show, hoverIntent);
|
1611 |
+
|
1612 |
+
// Prerendering is enabled, create tooltip now
|
1613 |
+
if(options.show.ready || options.prerender) { hoverIntent(event); }
|
1614 |
+
});
|
1615 |
+
};
|
1616 |
+
|
1617 |
+
// Setup base plugins
|
1618 |
+
PLUGINS = QTIP.plugins = {
|
1619 |
+
// Corner object parser
|
1620 |
+
Corner: function(corner) {
|
1621 |
+
corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
|
1622 |
+
this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
|
1623 |
+
this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
|
1624 |
+
|
1625 |
+
var f = corner.charAt(0); this.precedance = (f === 't' || f === 'b' ? Y : X);
|
1626 |
+
|
1627 |
+
this.string = function() { return this.precedance === Y ? this.y+this.x : this.x+this.y; };
|
1628 |
+
this.abbrev = function() {
|
1629 |
+
var x = this.x.substr(0,1), y = this.y.substr(0,1);
|
1630 |
+
return x === y ? x : this.precedance === Y ? y + x : x + y;
|
1631 |
+
};
|
1632 |
+
|
1633 |
+
this.invertx = function(center) { this.x = this.x === LEFT ? RIGHT : this.x === RIGHT ? LEFT : center || this.x; };
|
1634 |
+
this.inverty = function(center) { this.y = this.y === TOP ? BOTTOM : this.y === BOTTOM ? TOP : center || this.y; };
|
1635 |
+
|
1636 |
+
this.clone = function() {
|
1637 |
+
return {
|
1638 |
+
x: this.x, y: this.y, precedance: this.precedance,
|
1639 |
+
string: this.string, abbrev: this.abbrev, clone: this.clone,
|
1640 |
+
invertx: this.invertx, inverty: this.inverty
|
1641 |
+
};
|
1642 |
+
};
|
1643 |
+
},
|
1644 |
+
|
1645 |
+
// Custom (more correct for qTip!) offset calculator
|
1646 |
+
offset: function(elem, container) {
|
1647 |
+
var pos = elem.offset(),
|
1648 |
+
docBody = elem.closest('body')[0],
|
1649 |
+
parent = container, scrolled,
|
1650 |
+
coffset, overflow;
|
1651 |
+
|
1652 |
+
function scroll(e, i) {
|
1653 |
+
pos.left += i * e.scrollLeft();
|
1654 |
+
pos.top += i * e.scrollTop();
|
1655 |
+
}
|
1656 |
+
|
1657 |
+
if(parent) {
|
1658 |
+
// Compensate for non-static containers offset
|
1659 |
+
do {
|
1660 |
+
if(parent.css('position') !== 'static') {
|
1661 |
+
coffset = parent.position();
|
1662 |
+
|
1663 |
+
// Account for element positioning, borders and margins
|
1664 |
+
pos.left -= coffset.left + (parseInt(parent.css('borderLeftWidth'), 10) || 0) + (parseInt(parent.css('marginLeft'), 10) || 0);
|
1665 |
+
pos.top -= coffset.top + (parseInt(parent.css('borderTopWidth'), 10) || 0) + (parseInt(parent.css('marginTop'), 10) || 0);
|
1666 |
+
|
1667 |
+
// If this is the first parent element with an overflow of "scroll" or "auto", store it
|
1668 |
+
if(!scrolled && (overflow = parent.css('overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = parent; }
|
1669 |
+
}
|
1670 |
+
}
|
1671 |
+
while((parent = $(parent[0].offsetParent)).length);
|
1672 |
+
|
1673 |
+
// Compensate for containers scroll if it also has an offsetParent
|
1674 |
+
if(scrolled && scrolled[0] !== docBody) { scroll( scrolled, 1 ); }
|
1675 |
+
}
|
1676 |
+
|
1677 |
+
return pos;
|
1678 |
+
},
|
1679 |
+
|
1680 |
+
/*
|
1681 |
+
* iOS version detection
|
1682 |
+
*/
|
1683 |
+
iOS: parseFloat(
|
1684 |
+
('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
|
1685 |
+
.replace('undefined', '3_2').replace('_', '.').replace('_', '')
|
1686 |
+
) || FALSE,
|
1687 |
+
|
1688 |
+
/*
|
1689 |
+
* jQuery-specific $.fn overrides
|
1690 |
+
*/
|
1691 |
+
fn: {
|
1692 |
+
/* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
|
1693 |
+
attr: function(attr, val) {
|
1694 |
+
if(this.length) {
|
1695 |
+
var self = this[0],
|
1696 |
+
title = 'title',
|
1697 |
+
api = $.data(self, 'qtip');
|
1698 |
+
|
1699 |
+
if(attr === title && api && 'object' === typeof api && api.options.suppress) {
|
1700 |
+
if(arguments.length < 2) {
|
1701 |
+
return $.attr(self, oldtitle);
|
1702 |
+
}
|
1703 |
+
|
1704 |
+
// If qTip is rendered and title was originally used as content, update it
|
1705 |
+
if(api && api.options.content.attr === title && api.cache.attr) {
|
1706 |
+
api.set('content.text', val);
|
1707 |
+
}
|
1708 |
+
|
1709 |
+
// Use the regular attr method to set, then cache the result
|
1710 |
+
return this.attr(oldtitle, val);
|
1711 |
+
}
|
1712 |
+
}
|
1713 |
+
|
1714 |
+
return $.fn['attr'+replaceSuffix].apply(this, arguments);
|
1715 |
+
},
|
1716 |
+
|
1717 |
+
/* Allow clone to correctly retrieve cached title attributes */
|
1718 |
+
clone: function(keepData) {
|
1719 |
+
var titles = $([]), title = 'title',
|
1720 |
+
|
1721 |
+
// Clone our element using the real clone method
|
1722 |
+
elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
|
1723 |
+
|
1724 |
+
// Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
|
1725 |
+
if(!keepData) {
|
1726 |
+
elems.filter('['+oldtitle+']').attr('title', function() {
|
1727 |
+
return $.attr(this, oldtitle);
|
1728 |
+
})
|
1729 |
+
.removeAttr(oldtitle);
|
1730 |
+
}
|
1731 |
+
|
1732 |
+
return elems;
|
1733 |
+
}
|
1734 |
+
}
|
1735 |
+
};
|
1736 |
+
|
1737 |
+
// Apply the fn overrides above
|
1738 |
+
$.each(PLUGINS.fn, function(name, func) {
|
1739 |
+
if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
|
1740 |
+
|
1741 |
+
var old = $.fn[name+replaceSuffix] = $.fn[name];
|
1742 |
+
$.fn[name] = function() {
|
1743 |
+
return func.apply(this, arguments) || old.apply(this, arguments);
|
1744 |
+
};
|
1745 |
+
});
|
1746 |
+
|
1747 |
+
/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
|
1748 |
+
* This snippet is taken directly from jQuery UI source code found here:
|
1749 |
+
* http://code.jquery.com/ui/jquery-ui-git.js
|
1750 |
+
*/
|
1751 |
+
if(!$.ui) {
|
1752 |
+
$['cleanData'+replaceSuffix] = $.cleanData;
|
1753 |
+
$.cleanData = function( elems ) {
|
1754 |
+
for(var i = 0, elem; (elem = elems[i]) !== undefined; i++) {
|
1755 |
+
try { $( elem ).triggerHandler('removeqtip'); }
|
1756 |
+
catch( e ) {}
|
1757 |
+
}
|
1758 |
+
$['cleanData'+replaceSuffix]( elems );
|
1759 |
+
};
|
1760 |
+
}
|
1761 |
+
|
1762 |
+
// Set global qTip properties
|
1763 |
+
QTIP.version = '@VERSION';
|
1764 |
+
QTIP.nextid = 0;
|
1765 |
+
QTIP.inactiveEvents = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' ');
|
1766 |
+
QTIP.zindex = 15000;
|
1767 |
+
|
1768 |
+
// Define configuration defaults
|
1769 |
+
QTIP.defaults = {
|
1770 |
+
prerender: FALSE,
|
1771 |
+
id: FALSE,
|
1772 |
+
overwrite: TRUE,
|
1773 |
+
suppress: TRUE,
|
1774 |
+
content: {
|
1775 |
+
text: TRUE,
|
1776 |
+
attr: 'title',
|
1777 |
+
title: {
|
1778 |
+
text: FALSE,
|
1779 |
+
button: FALSE
|
1780 |
+
}
|
1781 |
+
},
|
1782 |
+
position: {
|
1783 |
+
my: 'top left',
|
1784 |
+
at: 'bottom right',
|
1785 |
+
target: FALSE,
|
1786 |
+
container: FALSE,
|
1787 |
+
viewport: FALSE,
|
1788 |
+
adjust: {
|
1789 |
+
x: 0, y: 0,
|
1790 |
+
mouse: TRUE,
|
1791 |
+
resize: TRUE,
|
1792 |
+
method: 'flip flip'
|
1793 |
+
},
|
1794 |
+
effect: function(api, pos, viewport) {
|
1795 |
+
$(this).animate(pos, {
|
1796 |
+
duration: 200,
|
1797 |
+
queue: FALSE
|
1798 |
+
});
|
1799 |
+
}
|
1800 |
+
},
|
1801 |
+
show: {
|
1802 |
+
target: FALSE,
|
1803 |
+
event: 'mouseenter',
|
1804 |
+
effect: TRUE,
|
1805 |
+
delay: 90,
|
1806 |
+
solo: FALSE,
|
1807 |
+
ready: FALSE,
|
1808 |
+
autofocus: FALSE
|
1809 |
+
},
|
1810 |
+
hide: {
|
1811 |
+
target: FALSE,
|
1812 |
+
event: 'mouseleave',
|
1813 |
+
effect: TRUE,
|
1814 |
+
delay: 0,
|
1815 |
+
fixed: FALSE,
|
1816 |
+
inactive: FALSE,
|
1817 |
+
leave: 'window',
|
1818 |
+
distance: FALSE
|
1819 |
+
},
|
1820 |
+
style: {
|
1821 |
+
classes: '',
|
1822 |
+
widget: FALSE,
|
1823 |
+
width: FALSE,
|
1824 |
+
height: FALSE,
|
1825 |
+
def: TRUE
|
1826 |
+
},
|
1827 |
+
events: {
|
1828 |
+
render: NULL,
|
1829 |
+
move: NULL,
|
1830 |
+
show: NULL,
|
1831 |
+
hide: NULL,
|
1832 |
+
toggle: NULL,
|
1833 |
+
visible: NULL,
|
1834 |
+
hidden: NULL,
|
1835 |
+
focus: NULL,
|
1836 |
+
blur: NULL
|
1837 |
+
}
|
1838 |
+
};
|
1839 |
+
|
1840 |
+
|
1841 |
+
PLUGINS.svg = function(api, svg, corner, adjustMethod)
|
1842 |
+
{
|
1843 |
+
var doc = $(document),
|
1844 |
+
elem = svg[0],
|
1845 |
+
result = {
|
1846 |
+
width: 0, height: 0,
|
1847 |
+
position: { top: 1e10, left: 1e10 }
|
1848 |
+
},
|
1849 |
+
box, mtx, root, point, tPoint;
|
1850 |
+
|
1851 |
+
// Ascend the parentNode chain until we find an element with getBBox()
|
1852 |
+
while(!elem.getBBox) { elem = elem.parentNode; }
|
1853 |
+
|
1854 |
+
// Check for a valid bounding box method
|
1855 |
+
if (elem.getBBox && elem.parentNode) {
|
1856 |
+
box = elem.getBBox();
|
1857 |
+
mtx = elem.getScreenCTM();
|
1858 |
+
root = elem.farthestViewportElement || elem;
|
1859 |
+
|
1860 |
+
// Return if no method is found
|
1861 |
+
if(!root.createSVGPoint) { return result; }
|
1862 |
+
|
1863 |
+
// Create our point var
|
1864 |
+
point = root.createSVGPoint();
|
1865 |
+
|
1866 |
+
// Adjust top and left
|
1867 |
+
point.x = box.x;
|
1868 |
+
point.y = box.y;
|
1869 |
+
tPoint = point.matrixTransform(mtx);
|
1870 |
+
result.position.left = tPoint.x;
|
1871 |
+
result.position.top = tPoint.y;
|
1872 |
+
|
1873 |
+
// Adjust width and height
|
1874 |
+
point.x += box.width;
|
1875 |
+
point.y += box.height;
|
1876 |
+
tPoint = point.matrixTransform(mtx);
|
1877 |
+
result.width = tPoint.x - result.position.left;
|
1878 |
+
result.height = tPoint.y - result.position.top;
|
1879 |
+
|
1880 |
+
// Adjust by scroll offset
|
1881 |
+
result.position.left += doc.scrollLeft();
|
1882 |
+
result.position.top += doc.scrollTop();
|
1883 |
+
}
|
1884 |
+
|
1885 |
+
return result;
|
1886 |
+
};
|
1887 |
+
|
1888 |
+
|
1889 |
+
function Ajax(api)
|
1890 |
+
{
|
1891 |
+
var self = this,
|
1892 |
+
tooltip = api.elements.tooltip,
|
1893 |
+
opts = api.options.content.ajax,
|
1894 |
+
defaults = QTIP.defaults.content.ajax,
|
1895 |
+
namespace = '.qtip-ajax',
|
1896 |
+
rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
1897 |
+
first = TRUE,
|
1898 |
+
stop = FALSE,
|
1899 |
+
xhr;
|
1900 |
+
|
1901 |
+
api.checks.ajax = {
|
1902 |
+
'^content.ajax': function(obj, name, v) {
|
1903 |
+
// If content.ajax object was reset, set our local var
|
1904 |
+
if(name === 'ajax') { opts = v; }
|
1905 |
+
|
1906 |
+
if(name === 'once') {
|
1907 |
+
self.init();
|
1908 |
+
}
|
1909 |
+
else if(opts && opts.url) {
|
1910 |
+
self.load();
|
1911 |
+
}
|
1912 |
+
else {
|
1913 |
+
tooltip.unbind(namespace);
|
1914 |
+
}
|
1915 |
+
}
|
1916 |
+
};
|
1917 |
+
|
1918 |
+
$.extend(self, {
|
1919 |
+
init: function() {
|
1920 |
+
// Make sure ajax options are enabled and bind event
|
1921 |
+
if(opts && opts.url) {
|
1922 |
+
tooltip.unbind(namespace)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+namespace, self.load);
|
1923 |
+
}
|
1924 |
+
|
1925 |
+
return self;
|
1926 |
+
},
|
1927 |
+
|
1928 |
+
load: function(event) {
|
1929 |
+
if(stop) {stop = FALSE; return; }
|
1930 |
+
|
1931 |
+
var hasSelector = opts.url.lastIndexOf(' '),
|
1932 |
+
url = opts.url,
|
1933 |
+
selector,
|
1934 |
+
hideFirst = !opts.loading && first;
|
1935 |
+
|
1936 |
+
// If loading option is disabled, prevent the tooltip showing until we've completed the request
|
1937 |
+
if(hideFirst) { try{ event.preventDefault(); } catch(e) {} }
|
1938 |
+
|
1939 |
+
// Make sure default event hasn't been prevented
|
1940 |
+
else if(event && event.isDefaultPrevented()) { return self; }
|
1941 |
+
|
1942 |
+
// Cancel old request
|
1943 |
+
if(xhr && xhr.abort) { xhr.abort(); }
|
1944 |
+
|
1945 |
+
// Check if user delcared a content selector like in .load()
|
1946 |
+
if(hasSelector > -1) {
|
1947 |
+
selector = url.substr(hasSelector);
|
1948 |
+
url = url.substr(0, hasSelector);
|
1949 |
+
}
|
1950 |
+
|
1951 |
+
// Define common after callback for both success/error handlers
|
1952 |
+
function after() {
|
1953 |
+
var complete;
|
1954 |
+
|
1955 |
+
// Don't proceed if tooltip is destroyed
|
1956 |
+
if(api.destroyed) { return; }
|
1957 |
+
|
1958 |
+
// Set first flag to false
|
1959 |
+
first = FALSE;
|
1960 |
+
|
1961 |
+
// Re-display tip if loading and first time, and reset first flag
|
1962 |
+
if(hideFirst) { stop = TRUE; api.show(event.originalEvent); }
|
1963 |
+
|
1964 |
+
// Call users complete method if it was defined
|
1965 |
+
if((complete = defaults.complete || opts.complete) && $.isFunction(complete)) {
|
1966 |
+
complete.apply(opts.context || api, arguments);
|
1967 |
+
}
|
1968 |
+
}
|
1969 |
+
|
1970 |
+
// Define success handler
|
1971 |
+
function successHandler(content, status, jqXHR) {
|
1972 |
+
var success;
|
1973 |
+
|
1974 |
+
// Don't proceed if tooltip is destroyed
|
1975 |
+
if(api.destroyed) { return; }
|
1976 |
+
|
1977 |
+
// If URL contains a selector
|
1978 |
+
if(selector && 'string' === typeof content) {
|
1979 |
+
// Create a dummy div to hold the results and grab the selector element
|
1980 |
+
content = $('<div/>')
|
1981 |
+
// inject the contents of the document in, removing the scripts
|
1982 |
+
// to avoid any 'Permission Denied' errors in IE
|
1983 |
+
.append(content.replace(rscript, ""))
|
1984 |
+
|
1985 |
+
// Locate the specified elements
|
1986 |
+
.find(selector);
|
1987 |
+
}
|
1988 |
+
|
1989 |
+
// Call the success function if one is defined
|
1990 |
+
if((success = defaults.success || opts.success) && $.isFunction(success)) {
|
1991 |
+
success.call(opts.context || api, content, status, jqXHR);
|
1992 |
+
}
|
1993 |
+
|
1994 |
+
// Otherwise set the content
|
1995 |
+
else { api.set('content.text', content); }
|
1996 |
+
}
|
1997 |
+
|
1998 |
+
// Error handler
|
1999 |
+
function errorHandler(xhr, status, error) {
|
2000 |
+
if(api.destroyed || xhr.status === 0) { return; }
|
2001 |
+
api.set('content.text', status + ': ' + error);
|
2002 |
+
}
|
2003 |
+
|
2004 |
+
// Setup $.ajax option object and process the request
|
2005 |
+
xhr = $.ajax(
|
2006 |
+
$.extend({
|
2007 |
+
error: defaults.error || errorHandler,
|
2008 |
+
context: api
|
2009 |
+
},
|
2010 |
+
opts, { url: url, success: successHandler, complete: after })
|
2011 |
+
);
|
2012 |
+
},
|
2013 |
+
|
2014 |
+
destroy: function() {
|
2015 |
+
// Cancel ajax request if possible
|
2016 |
+
if(xhr && xhr.abort) { xhr.abort(); }
|
2017 |
+
|
2018 |
+
// Set api.destroyed flag
|
2019 |
+
api.destroyed = TRUE;
|
2020 |
+
}
|
2021 |
+
});
|
2022 |
+
|
2023 |
+
self.init();
|
2024 |
+
}
|
2025 |
+
|
2026 |
+
|
2027 |
+
PLUGINS.ajax = function(api)
|
2028 |
+
{
|
2029 |
+
var self = api.plugins.ajax;
|
2030 |
+
|
2031 |
+
return 'object' === typeof self ? self : (api.plugins.ajax = new Ajax(api));
|
2032 |
+
};
|
2033 |
+
|
2034 |
+
PLUGINS.ajax.initialize = 'render';
|
2035 |
+
|
2036 |
+
// Setup plugin sanitization
|
2037 |
+
PLUGINS.ajax.sanitize = function(options)
|
2038 |
+
{
|
2039 |
+
var content = options.content, opts;
|
2040 |
+
if(content && 'ajax' in content) {
|
2041 |
+
opts = content.ajax;
|
2042 |
+
if(typeof opts !== 'object') { opts = options.content.ajax = { url: opts }; }
|
2043 |
+
if('boolean' !== typeof opts.once && opts.once) { opts.once = !!opts.once; }
|
2044 |
+
}
|
2045 |
+
};
|
2046 |
+
|
2047 |
+
// Extend original api defaults
|
2048 |
+
$.extend(TRUE, QTIP.defaults, {
|
2049 |
+
content: {
|
2050 |
+
ajax: {
|
2051 |
+
loading: TRUE,
|
2052 |
+
once: TRUE
|
2053 |
+
}
|
2054 |
+
}
|
2055 |
+
});
|
2056 |
+
|
2057 |
+
|
2058 |
+
// Tip coordinates calculator
|
2059 |
+
function calculateTip(corner, width, height)
|
2060 |
+
{
|
2061 |
+
var width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),
|
2062 |
+
|
2063 |
+
// Define tip coordinates in terms of height and width values
|
2064 |
+
tips = {
|
2065 |
+
bottomright: [[0,0], [width,height], [width,0]],
|
2066 |
+
bottomleft: [[0,0], [width,0], [0,height]],
|
2067 |
+
topright: [[0,height], [width,0], [width,height]],
|
2068 |
+
topleft: [[0,0], [0,height], [width,height]],
|
2069 |
+
topcenter: [[0,height], [width2,0], [width,height]],
|
2070 |
+
bottomcenter: [[0,0], [width,0], [width2,height]],
|
2071 |
+
rightcenter: [[0,0], [width,height2], [0,height]],
|
2072 |
+
leftcenter: [[width,0], [width,height], [0,height2]]
|
2073 |
+
};
|
2074 |
+
|
2075 |
+
// Set common side shapes
|
2076 |
+
tips.lefttop = tips.bottomright; tips.righttop = tips.bottomleft;
|
2077 |
+
tips.leftbottom = tips.topright; tips.rightbottom = tips.topleft;
|
2078 |
+
|
2079 |
+
return tips[ corner.string() ];
|
2080 |
+
}
|
2081 |
+
|
2082 |
+
|
2083 |
+
function Tip(qTip, command)
|
2084 |
+
{
|
2085 |
+
var self = this,
|
2086 |
+
opts = qTip.options.style.tip,
|
2087 |
+
elems = qTip.elements,
|
2088 |
+
tooltip = elems.tooltip,
|
2089 |
+
cache = { top: 0, left: 0 },
|
2090 |
+
size = {
|
2091 |
+
width: opts.width,
|
2092 |
+
height: opts.height
|
2093 |
+
},
|
2094 |
+
color = { },
|
2095 |
+
border = opts.border || 0,
|
2096 |
+
namespace = '.qtip-tip',
|
2097 |
+
hasCanvas = !!($('<canvas />')[0] || {}).getContext,
|
2098 |
+
tiphtml;
|
2099 |
+
|
2100 |
+
self.corner = NULL;
|
2101 |
+
self.mimic = NULL;
|
2102 |
+
self.border = border;
|
2103 |
+
self.offset = opts.offset;
|
2104 |
+
self.size = size;
|
2105 |
+
|
2106 |
+
// Add new option checks for the plugin
|
2107 |
+
qTip.checks.tip = {
|
2108 |
+
'^position.my|style.tip.(corner|mimic|border)$': function() {
|
2109 |
+
// Make sure a tip can be drawn
|
2110 |
+
if(!self.init()) {
|
2111 |
+
self.destroy();
|
2112 |
+
}
|
2113 |
+
|
2114 |
+
// Reposition the tooltip
|
2115 |
+
qTip.reposition();
|
2116 |
+
},
|
2117 |
+
'^style.tip.(height|width)$': function() {
|
2118 |
+
// Re-set dimensions and redraw the tip
|
2119 |
+
size = {
|
2120 |
+
width: opts.width,
|
2121 |
+
height: opts.height
|
2122 |
+
};
|
2123 |
+
self.create();
|
2124 |
+
self.update();
|
2125 |
+
|
2126 |
+
// Reposition the tooltip
|
2127 |
+
qTip.reposition();
|
2128 |
+
},
|
2129 |
+
'^content.title.text|style.(classes|widget)$': function() {
|
2130 |
+
if(elems.tip && elems.tip.length) {
|
2131 |
+
self.update();
|
2132 |
+
}
|
2133 |
+
}
|
2134 |
+
};
|
2135 |
+
|
2136 |
+
function whileVisible(callback) {
|
2137 |
+
var visible = tooltip.is(':visible');
|
2138 |
+
tooltip.show(); callback(); tooltip.toggle(visible);
|
2139 |
+
}
|
2140 |
+
|
2141 |
+
function swapDimensions() {
|
2142 |
+
size.width = opts.height;
|
2143 |
+
size.height = opts.width;
|
2144 |
+
}
|
2145 |
+
|
2146 |
+
function resetDimensions() {
|
2147 |
+
size.width = opts.width;
|
2148 |
+
size.height = opts.height;
|
2149 |
+
}
|
2150 |
+
|
2151 |
+
function reposition(event, api, pos, viewport) {
|
2152 |
+
if(!elems.tip) { return; }
|
2153 |
+
|
2154 |
+
var newCorner = self.corner.clone(),
|
2155 |
+
adjust = pos.adjusted,
|
2156 |
+
method = qTip.options.position.adjust.method.split(' '),
|
2157 |
+
horizontal = method[0],
|
2158 |
+
vertical = method[1] || method[0],
|
2159 |
+
shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
|
2160 |
+
offset, css = {}, props;
|
2161 |
+
|
2162 |
+
// If our tip position isn't fixed e.g. doesn't adjust with viewport...
|
2163 |
+
if(self.corner.fixed !== TRUE) {
|
2164 |
+
// Horizontal - Shift or flip method
|
2165 |
+
if(horizontal === SHIFT && newCorner.precedance === X && adjust.left && newCorner.y !== CENTER) {
|
2166 |
+
newCorner.precedance = newCorner.precedance === X ? Y : X;
|
2167 |
+
}
|
2168 |
+
else if(horizontal !== SHIFT && adjust.left){
|
2169 |
+
newCorner.x = newCorner.x === CENTER ? (adjust.left > 0 ? LEFT : RIGHT) : (newCorner.x === LEFT ? RIGHT : LEFT);
|
2170 |
+
}
|
2171 |
+
|
2172 |
+
// Vertical - Shift or flip method
|
2173 |
+
if(vertical === SHIFT && newCorner.precedance === Y && adjust.top && newCorner.x !== CENTER) {
|
2174 |
+
newCorner.precedance = newCorner.precedance === Y ? X : Y;
|
2175 |
+
}
|
2176 |
+
else if(vertical !== SHIFT && adjust.top) {
|
2177 |
+
newCorner.y = newCorner.y === CENTER ? (adjust.top > 0 ? TOP : BOTTOM) : (newCorner.y === TOP ? BOTTOM : TOP);
|
2178 |
+
}
|
2179 |
+
|
2180 |
+
// Update and redraw the tip if needed (check cached details of last drawn tip)
|
2181 |
+
if(newCorner.string() !== cache.corner.string() && (cache.top !== adjust.top || cache.left !== adjust.left)) {
|
2182 |
+
self.update(newCorner, FALSE);
|
2183 |
+
}
|
2184 |
+
}
|
2185 |
+
|
2186 |
+
// Setup tip offset properties
|
2187 |
+
offset = self.position(newCorner, adjust);
|
2188 |
+
offset[ newCorner.x ] += parseWidth(newCorner, newCorner.x);
|
2189 |
+
offset[ newCorner.y ] += parseWidth(newCorner, newCorner.y);
|
2190 |
+
|
2191 |
+
// Readjust offset object to make it left/top
|
2192 |
+
if(offset.right !== undefined) { offset.left = -offset.right; }
|
2193 |
+
if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
|
2194 |
+
offset.user = Math.max(0, opts.offset);
|
2195 |
+
|
2196 |
+
// Viewport "shift" specific adjustments
|
2197 |
+
if(shift.left = (horizontal === SHIFT && !!adjust.left)) {
|
2198 |
+
if(newCorner.x === CENTER) {
|
2199 |
+
css['margin-left'] = shift.x = offset['margin-left'] - adjust.left;
|
2200 |
+
}
|
2201 |
+
else {
|
2202 |
+
props = offset.right !== undefined ?
|
2203 |
+
[ adjust.left, -offset.left ] : [ -adjust.left, offset.left ];
|
2204 |
+
|
2205 |
+
if( (shift.x = Math.max(props[0], props[1])) > props[0] ) {
|
2206 |
+
pos.left -= adjust.left;
|
2207 |
+
shift.left = FALSE;
|
2208 |
+
}
|
2209 |
+
|
2210 |
+
css[ offset.right !== undefined ? RIGHT : LEFT ] = shift.x;
|
2211 |
+
}
|
2212 |
+
}
|
2213 |
+
if(shift.top = (vertical === SHIFT && !!adjust.top)) {
|
2214 |
+
if(newCorner.y === CENTER) {
|
2215 |
+
css['margin-top'] = shift.y = offset['margin-top'] - adjust.top;
|
2216 |
+
}
|
2217 |
+
else {
|
2218 |
+
props = offset.bottom !== undefined ?
|
2219 |
+
[ adjust.top, -offset.top ] : [ -adjust.top, offset.top ];
|
2220 |
+
|
2221 |
+
if( (shift.y = Math.max(props[0], props[1])) > props[0] ) {
|
2222 |
+
pos.top -= adjust.top;
|
2223 |
+
shift.top = FALSE;
|
2224 |
+
}
|
2225 |
+
|
2226 |
+
css[ offset.bottom !== undefined ? BOTTOM : TOP ] = shift.y;
|
2227 |
+
}
|
2228 |
+
}
|
2229 |
+
|
2230 |
+
/*
|
2231 |
+
* If the tip is adjusted in both dimensions, or in a
|
2232 |
+
* direction that would cause it to be anywhere but the
|
2233 |
+
* outer border, hide it!
|
2234 |
+
*/
|
2235 |
+
elems.tip.css(css).toggle(
|
2236 |
+
!((shift.x && shift.y) || (newCorner.x === CENTER && shift.y) || (newCorner.y === CENTER && shift.x))
|
2237 |
+
);
|
2238 |
+
|
2239 |
+
// Adjust position to accomodate tip dimensions
|
2240 |
+
pos.left -= offset.left.charAt ? offset.user : horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left : 0;
|
2241 |
+
pos.top -= offset.top.charAt ? offset.user : vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top : 0;
|
2242 |
+
|
2243 |
+
// Cache details
|
2244 |
+
cache.left = adjust.left; cache.top = adjust.top;
|
2245 |
+
cache.corner = newCorner.clone();
|
2246 |
+
}
|
2247 |
+
|
2248 |
+
function parseCorner() {
|
2249 |
+
var corner = opts.corner,
|
2250 |
+
posOptions = qTip.options.position,
|
2251 |
+
at = posOptions.at,
|
2252 |
+
my = posOptions.my.string ? posOptions.my.string() : posOptions.my;
|
2253 |
+
|
2254 |
+
// Detect corner and mimic properties
|
2255 |
+
if(corner === FALSE || (my === FALSE && at === FALSE)) {
|
2256 |
+
return FALSE;
|
2257 |
+
}
|
2258 |
+
else {
|
2259 |
+
if(corner === TRUE) {
|
2260 |
+
self.corner = new PLUGINS.Corner(my);
|
2261 |
+
}
|
2262 |
+
else if(!corner.string) {
|
2263 |
+
self.corner = new PLUGINS.Corner(corner);
|
2264 |
+
self.corner.fixed = TRUE;
|
2265 |
+
}
|
2266 |
+
}
|
2267 |
+
|
2268 |
+
// Cache it
|
2269 |
+
cache.corner = new PLUGINS.Corner( self.corner.string() );
|
2270 |
+
|
2271 |
+
return self.corner.string() !== 'centercenter';
|
2272 |
+
}
|
2273 |
+
|
2274 |
+
/* border width calculator */
|
2275 |
+
function parseWidth(corner, side, use) {
|
2276 |
+
side = !side ? corner[corner.precedance] : side;
|
2277 |
+
|
2278 |
+
var isTitleTop = elems.titlebar && corner.y === TOP,
|
2279 |
+
elem = isTitleTop ? elems.titlebar : tooltip,
|
2280 |
+
borderSide = 'border-' + side + '-width',
|
2281 |
+
css = function(elem) { return parseInt(elem.css(borderSide), 10); },
|
2282 |
+
val;
|
2283 |
+
|
2284 |
+
// Grab the border-width value (make tooltip visible first)
|
2285 |
+
whileVisible(function() {
|
2286 |
+
val = (use ? css(use) : (css(elems.content) || css(elem) || css(tooltip))) || 0;
|
2287 |
+
});
|
2288 |
+
return val;
|
2289 |
+
}
|
2290 |
+
|
2291 |
+
function parseRadius(corner) {
|
2292 |
+
var isTitleTop = elems.titlebar && corner.y === TOP,
|
2293 |
+
elem = isTitleTop ? elems.titlebar : elems.content,
|
2294 |
+
moz = $.browser.mozilla,
|
2295 |
+
prefix = moz ? '-moz-' : $.browser.webkit ? '-webkit-' : '',
|
2296 |
+
nonStandard = 'border-radius-' + corner.y + corner.x,
|
2297 |
+
standard = 'border-' + corner.y + '-' + corner.x + '-radius',
|
2298 |
+
css = function(c) { return parseInt(elem.css(c), 10) || parseInt(tooltip.css(c), 10); },
|
2299 |
+
val;
|
2300 |
+
|
2301 |
+
whileVisible(function() {
|
2302 |
+
val = css(standard) || css(prefix + standard) || css(prefix + nonStandard) || css(nonStandard) || 0;
|
2303 |
+
});
|
2304 |
+
return val;
|
2305 |
+
}
|
2306 |
+
|
2307 |
+
function parseColours(actual) {
|
2308 |
+
var i, fill, border,
|
2309 |
+
tip = elems.tip.css('cssText', ''),
|
2310 |
+
corner = actual || self.corner,
|
2311 |
+
invalid = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,
|
2312 |
+
borderSide = 'border-' + corner[ corner.precedance ] + '-color',
|
2313 |
+
bgColor = 'background-color',
|
2314 |
+
transparent = 'transparent',
|
2315 |
+
important = ' !important',
|
2316 |
+
|
2317 |
+
titlebar = elems.titlebar,
|
2318 |
+
useTitle = titlebar && (corner.y === TOP || (corner.y === CENTER && tip.position().top + (size.height / 2) + opts.offset < titlebar.outerHeight(TRUE))),
|
2319 |
+
colorElem = useTitle ? titlebar : elems.content;
|
2320 |
+
|
2321 |
+
function css(elem, prop, compare) {
|
2322 |
+
var val = elem.css(prop) || transparent;
|
2323 |
+
if(compare && val === elem.css(compare)) { return FALSE; }
|
2324 |
+
else { return invalid.test(val) ? FALSE : val; }
|
2325 |
+
}
|
2326 |
+
|
2327 |
+
// Ensure tooltip is visible then...
|
2328 |
+
whileVisible(function() {
|
2329 |
+
// Attempt to detect the background colour from various elements, left-to-right precedance
|
2330 |
+
color.fill = css(tip, bgColor) || css(colorElem, bgColor) || css(elems.content, bgColor) ||
|
2331 |
+
css(tooltip, bgColor) || tip.css(bgColor);
|
2332 |
+
|
2333 |
+
// Attempt to detect the correct border side colour from various elements, left-to-right precedance
|
2334 |
+
color.border = css(tip, borderSide, 'color') || css(colorElem, borderSide, 'color') ||
|
2335 |
+
css(elems.content, borderSide, 'color') || css(tooltip, borderSide, 'color') || tooltip.css(borderSide);
|
2336 |
+
|
2337 |
+
// Reset background and border colours
|
2338 |
+
$('*', tip).add(tip).css('cssText', bgColor+':'+transparent+important+';border:0'+important+';');
|
2339 |
+
});
|
2340 |
+
}
|
2341 |
+
|
2342 |
+
function calculateSize(corner) {
|
2343 |
+
var y = corner.precedance === Y,
|
2344 |
+
width = size [ y ? WIDTH : HEIGHT ],
|
2345 |
+
height = size [ y ? HEIGHT : WIDTH ],
|
2346 |
+
isCenter = corner.string().indexOf(CENTER) > -1,
|
2347 |
+
base = width * (isCenter ? 0.5 : 1),
|
2348 |
+
pow = Math.pow,
|
2349 |
+
round = Math.round,
|
2350 |
+
bigHyp, ratio, result,
|
2351 |
+
|
2352 |
+
smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
|
2353 |
+
|
2354 |
+
hyp = [
|
2355 |
+
(border / base) * smallHyp, (border / height) * smallHyp
|
2356 |
+
];
|
2357 |
+
hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(border, 2) );
|
2358 |
+
hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(border, 2) );
|
2359 |
+
|
2360 |
+
bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
|
2361 |
+
ratio = bigHyp / smallHyp;
|
2362 |
+
|
2363 |
+
result = [ round(ratio * height), round(ratio * width) ];
|
2364 |
+
return { height: result[ y ? 0 : 1 ], width: result[ y ? 1 : 0 ] };
|
2365 |
+
}
|
2366 |
+
|
2367 |
+
function createVML(tag, props, style) {
|
2368 |
+
return '<qvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+
|
2369 |
+
' style="behavior: url(#default#VML); '+(style||'')+ '" />';
|
2370 |
+
}
|
2371 |
+
|
2372 |
+
$.extend(self, {
|
2373 |
+
init: function()
|
2374 |
+
{
|
2375 |
+
var enabled = parseCorner() && (hasCanvas || $.browser.msie);
|
2376 |
+
|
2377 |
+
// Determine tip corner and type
|
2378 |
+
if(enabled) {
|
2379 |
+
// Create a new tip and draw it
|
2380 |
+
self.create();
|
2381 |
+
self.update();
|
2382 |
+
|
2383 |
+
// Bind update events
|
2384 |
+
tooltip.unbind(namespace).bind('tooltipmove'+namespace, reposition);
|
2385 |
+
|
2386 |
+
// Fix for issue of tips not showing after redraw in IE (VML...)
|
2387 |
+
if(!hasCanvas) {
|
2388 |
+
tooltip.bind('tooltipredraw tooltipredrawn', function(event) {
|
2389 |
+
if(event.type === 'tooltipredraw') {
|
2390 |
+
tiphtml = elems.tip.html();
|
2391 |
+
elems.tip.html('');
|
2392 |
+
}
|
2393 |
+
else { elems.tip.html(tiphtml); }
|
2394 |
+
});
|
2395 |
+
}
|
2396 |
+
}
|
2397 |
+
|
2398 |
+
return enabled;
|
2399 |
+
},
|
2400 |
+
|
2401 |
+
create: function()
|
2402 |
+
{
|
2403 |
+
var width = size.width,
|
2404 |
+
height = size.height,
|
2405 |
+
vml;
|
2406 |
+
|
2407 |
+
// Remove previous tip element if present
|
2408 |
+
if(elems.tip) { elems.tip.remove(); }
|
2409 |
+
|
2410 |
+
// Create tip element and prepend to the tooltip
|
2411 |
+
elems.tip = $('<div />', { 'class': 'ui-tooltip-tip' }).css({ width: width, height: height }).prependTo(tooltip);
|
2412 |
+
|
2413 |
+
// Create tip drawing element(s)
|
2414 |
+
if(hasCanvas) {
|
2415 |
+
// save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
|
2416 |
+
$('<canvas />').appendTo(elems.tip)[0].getContext('2d').save();
|
2417 |
+
}
|
2418 |
+
else {
|
2419 |
+
vml = createVML('shape', 'coordorigin="0,0"', 'position:absolute;');
|
2420 |
+
elems.tip.html(vml + vml);
|
2421 |
+
|
2422 |
+
// Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML
|
2423 |
+
$('*', elems.tip).bind('click mousedown', function(event) { event.stopPropagation(); });
|
2424 |
+
}
|
2425 |
+
},
|
2426 |
+
|
2427 |
+
update: function(corner, position)
|
2428 |
+
{
|
2429 |
+
var tip = elems.tip,
|
2430 |
+
inner = tip.children(),
|
2431 |
+
width = size.width,
|
2432 |
+
height = size.height,
|
2433 |
+
mimic = opts.mimic,
|
2434 |
+
round = Math.round,
|
2435 |
+
precedance, context, coords, translate, newSize;
|
2436 |
+
|
2437 |
+
// Re-determine tip if not already set
|
2438 |
+
if(!corner) { corner = cache.corner || self.corner; }
|
2439 |
+
|
2440 |
+
// Use corner property if we detect an invalid mimic value
|
2441 |
+
if(mimic === FALSE) { mimic = corner; }
|
2442 |
+
|
2443 |
+
// Otherwise inherit mimic properties from the corner object as necessary
|
2444 |
+
else {
|
2445 |
+
mimic = new PLUGINS.Corner(mimic);
|
2446 |
+
mimic.precedance = corner.precedance;
|
2447 |
+
|
2448 |
+
if(mimic.x === 'inherit') { mimic.x = corner.x; }
|
2449 |
+
else if(mimic.y === 'inherit') { mimic.y = corner.y; }
|
2450 |
+
else if(mimic.x === mimic.y) {
|
2451 |
+
mimic[ corner.precedance ] = corner[ corner.precedance ];
|
2452 |
+
}
|
2453 |
+
}
|
2454 |
+
precedance = mimic.precedance;
|
2455 |
+
|
2456 |
+
// Ensure the tip width.height are relative to the tip position
|
2457 |
+
if(corner.precedance === X) { swapDimensions(); }
|
2458 |
+
else { resetDimensions(); }
|
2459 |
+
|
2460 |
+
// Set the tip dimensions
|
2461 |
+
elems.tip.css({
|
2462 |
+
width: (width = size.width),
|
2463 |
+
height: (height = size.height)
|
2464 |
+
});
|
2465 |
+
|
2466 |
+
// Update our colours
|
2467 |
+
parseColours(corner);
|
2468 |
+
|
2469 |
+
// Detect border width, taking into account colours
|
2470 |
+
if(color.border !== 'transparent') {
|
2471 |
+
// Grab border width
|
2472 |
+
border = parseWidth(corner, NULL);
|
2473 |
+
|
2474 |
+
// If border width isn't zero, use border color as fill (1.0 style tips)
|
2475 |
+
if(opts.border === 0 && border > 0) { color.fill = color.border; }
|
2476 |
+
|
2477 |
+
// Set border width (use detected border width if opts.border is true)
|
2478 |
+
self.border = border = opts.border !== TRUE ? opts.border : border;
|
2479 |
+
}
|
2480 |
+
|
2481 |
+
// Border colour was invalid, set border to zero
|
2482 |
+
else { self.border = border = 0; }
|
2483 |
+
|
2484 |
+
// Calculate coordinates
|
2485 |
+
coords = calculateTip(mimic, width , height);
|
2486 |
+
|
2487 |
+
// Determine tip size
|
2488 |
+
self.size = newSize = calculateSize(corner);
|
2489 |
+
tip.css(newSize);
|
2490 |
+
|
2491 |
+
// Calculate tip translation
|
2492 |
+
if(corner.precedance === Y) {
|
2493 |
+
translate = [
|
2494 |
+
round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize.width - width - border : (newSize.width - width) / 2),
|
2495 |
+
round(mimic.y === TOP ? newSize.height - height : 0)
|
2496 |
+
];
|
2497 |
+
}
|
2498 |
+
else {
|
2499 |
+
translate = [
|
2500 |
+
round(mimic.x === LEFT ? newSize.width - width : 0),
|
2501 |
+
round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize.height - height - border : (newSize.height - height) / 2)
|
2502 |
+
];
|
2503 |
+
}
|
2504 |
+
|
2505 |
+
// Canvas drawing implementation
|
2506 |
+
if(hasCanvas) {
|
2507 |
+
// Set the canvas size using calculated size
|
2508 |
+
inner.attr(newSize);
|
2509 |
+
|
2510 |
+
// Grab canvas context and clear/save it
|
2511 |
+
context = inner[0].getContext('2d');
|
2512 |
+
context.restore(); context.save();
|
2513 |
+
context.clearRect(0,0,3000,3000);
|
2514 |
+
|
2515 |
+
// Set properties
|
2516 |
+
context.fillStyle = color.fill;
|
2517 |
+
context.strokeStyle = color.border;
|
2518 |
+
context.lineWidth = border * 2;
|
2519 |
+
context.lineJoin = 'miter';
|
2520 |
+
context.miterLimit = 100;
|
2521 |
+
|
2522 |
+
// Translate origin
|
2523 |
+
context.translate(translate[0], translate[1]);
|
2524 |
+
|
2525 |
+
// Draw the tip
|
2526 |
+
context.beginPath();
|
2527 |
+
context.moveTo(coords[0][0], coords[0][1]);
|
2528 |
+
context.lineTo(coords[1][0], coords[1][1]);
|
2529 |
+
context.lineTo(coords[2][0], coords[2][1]);
|
2530 |
+
context.closePath();
|
2531 |
+
|
2532 |
+
// Apply fill and border
|
2533 |
+
if(border) {
|
2534 |
+
// Make sure transparent borders are supported by doing a stroke
|
2535 |
+
// of the background colour before the stroke colour
|
2536 |
+
if(tooltip.css('background-clip') === 'border-box') {
|
2537 |
+
context.strokeStyle = color.fill;
|
2538 |
+
context.stroke();
|
2539 |
+
}
|
2540 |
+
context.strokeStyle = color.border;
|
2541 |
+
context.stroke();
|
2542 |
+
}
|
2543 |
+
context.fill();
|
2544 |
+
}
|
2545 |
+
|
2546 |
+
// VML (IE Proprietary implementation)
|
2547 |
+
else {
|
2548 |
+
// Setup coordinates string
|
2549 |
+
coords = 'm' + coords[0][0] + ',' + coords[0][1] + ' l' + coords[1][0] +
|
2550 |
+
',' + coords[1][1] + ' ' + coords[2][0] + ',' + coords[2][1] + ' xe';
|
2551 |
+
|
2552 |
+
// Setup VML-specific offset for pixel-perfection
|
2553 |
+
translate[2] = border && /^(r|b)/i.test(corner.string()) ?
|
2554 |
+
parseFloat($.browser.version, 10) === 8 ? 2 : 1 : 0;
|
2555 |
+
|
2556 |
+
// Set initial CSS
|
2557 |
+
inner.css({
|
2558 |
+
coordsize: (width+border) + ' ' + (height+border),
|
2559 |
+
antialias: ''+(mimic.string().indexOf(CENTER) > -1),
|
2560 |
+
left: translate[0],
|
2561 |
+
top: translate[1],
|
2562 |
+
width: width + border,
|
2563 |
+
height: height + border
|
2564 |
+
})
|
2565 |
+
.each(function(i) {
|
2566 |
+
var $this = $(this);
|
2567 |
+
|
2568 |
+
// Set shape specific attributes
|
2569 |
+
$this[ $this.prop ? 'prop' : 'attr' ]({
|
2570 |
+
coordsize: (width+border) + ' ' + (height+border),
|
2571 |
+
path: coords,
|
2572 |
+
fillcolor: color.fill,
|
2573 |
+
filled: !!i,
|
2574 |
+
stroked: !i
|
2575 |
+
})
|
2576 |
+
.toggle(!!(border || i));
|
2577 |
+
|
2578 |
+
// Check if border is enabled and add stroke element
|
2579 |
+
if(!i && $this.html() === '') {
|
2580 |
+
$this.html(
|
2581 |
+
createVML('stroke', 'weight="'+(border*2)+'px" color="'+color.border+'" miterlimit="1000" joinstyle="miter"')
|
2582 |
+
);
|
2583 |
+
}
|
2584 |
+
});
|
2585 |
+
}
|
2586 |
+
|
2587 |
+
// Position if needed
|
2588 |
+
if(position !== FALSE) { self.position(corner); }
|
2589 |
+
},
|
2590 |
+
|
2591 |
+
// Tip positioning method
|
2592 |
+
position: function(corner)
|
2593 |
+
{
|
2594 |
+
var tip = elems.tip,
|
2595 |
+
position = {},
|
2596 |
+
userOffset = Math.max(0, opts.offset),
|
2597 |
+
precedance, dimensions, corners;
|
2598 |
+
|
2599 |
+
// Return if tips are disabled or tip is not yet rendered
|
2600 |
+
if(opts.corner === FALSE || !tip) { return FALSE; }
|
2601 |
+
|
2602 |
+
// Inherit corner if not provided
|
2603 |
+
corner = corner || self.corner;
|
2604 |
+
precedance = corner.precedance;
|
2605 |
+
|
2606 |
+
// Determine which tip dimension to use for adjustment
|
2607 |
+
dimensions = calculateSize(corner);
|
2608 |
+
|
2609 |
+
// Setup corners and offset array
|
2610 |
+
corners = [ corner.x, corner.y ];
|
2611 |
+
if(precedance === X) { corners.reverse(); }
|
2612 |
+
|
2613 |
+
// Calculate tip position
|
2614 |
+
$.each(corners, function(i, side) {
|
2615 |
+
var b, bc, br;
|
2616 |
+
|
2617 |
+
if(side === CENTER) {
|
2618 |
+
b = precedance === Y ? LEFT : TOP;
|
2619 |
+
position[ b ] = '50%';
|
2620 |
+
position['margin-' + b] = -Math.round(dimensions[ precedance === Y ? WIDTH : HEIGHT ] / 2) + userOffset;
|
2621 |
+
}
|
2622 |
+
else {
|
2623 |
+
b = parseWidth(corner, side);
|
2624 |
+
bc = parseWidth(corner, side, elems.content);
|
2625 |
+
br = parseRadius(corner);
|
2626 |
+
|
2627 |
+
position[ side ] = i ? bc : (userOffset + (br > b ? br : -b));
|
2628 |
+
}
|
2629 |
+
});
|
2630 |
+
|
2631 |
+
// Adjust for tip dimensions
|
2632 |
+
position[ corner[precedance] ] -= dimensions[ precedance === X ? WIDTH : HEIGHT ];
|
2633 |
+
|
2634 |
+
// Set and return new position
|
2635 |
+
tip.css({ top: '', bottom: '', left: '', right: '', margin: '' }).css(position);
|
2636 |
+
return position;
|
2637 |
+
},
|
2638 |
+
|
2639 |
+
destroy: function()
|
2640 |
+
{
|
2641 |
+
// Remove the tip element
|
2642 |
+
if(elems.tip) { elems.tip.remove(); }
|
2643 |
+
elems.tip = false;
|
2644 |
+
|
2645 |
+
// Unbind events
|
2646 |
+
tooltip.unbind(namespace);
|
2647 |
+
}
|
2648 |
+
});
|
2649 |
+
|
2650 |
+
self.init();
|
2651 |
+
}
|
2652 |
+
|
2653 |
+
PLUGINS.tip = function(api)
|
2654 |
+
{
|
2655 |
+
var self = api.plugins.tip;
|
2656 |
+
|
2657 |
+
return 'object' === typeof self ? self : (api.plugins.tip = new Tip(api));
|
2658 |
+
};
|
2659 |
+
|
2660 |
+
// Initialize tip on render
|
2661 |
+
PLUGINS.tip.initialize = 'render';
|
2662 |
+
|
2663 |
+
// Setup plugin sanitization options
|
2664 |
+
PLUGINS.tip.sanitize = function(options)
|
2665 |
+
{
|
2666 |
+
var style = options.style, opts;
|
2667 |
+
if(style && 'tip' in style) {
|
2668 |
+
opts = options.style.tip;
|
2669 |
+
if(typeof opts !== 'object'){ options.style.tip = { corner: opts }; }
|
2670 |
+
if(!(/string|boolean/i).test(typeof opts['corner'])) { opts['corner'] = TRUE; }
|
2671 |
+
if(typeof opts.width !== 'number'){ delete opts.width; }
|
2672 |
+
if(typeof opts.height !== 'number'){ delete opts.height; }
|
2673 |
+
if(typeof opts.border !== 'number' && opts.border !== TRUE){ delete opts.border; }
|
2674 |
+
if(typeof opts.offset !== 'number'){ delete opts.offset; }
|
2675 |
+
}
|
2676 |
+
};
|
2677 |
+
|
2678 |
+
// Extend original qTip defaults
|
2679 |
+
$.extend(TRUE, QTIP.defaults, {
|
2680 |
+
style: {
|
2681 |
+
tip: {
|
2682 |
+
corner: TRUE,
|
2683 |
+
mimic: FALSE,
|
2684 |
+
width: 6,
|
2685 |
+
height: 6,
|
2686 |
+
border: TRUE,
|
2687 |
+
offset: 0
|
2688 |
+
}
|
2689 |
+
}
|
2690 |
+
});
|
2691 |
+
|
2692 |
+
|
2693 |
+
function Modal(api)
|
2694 |
+
{
|
2695 |
+
var self = this,
|
2696 |
+
options = api.options.show.modal,
|
2697 |
+
elems = api.elements,
|
2698 |
+
tooltip = elems.tooltip,
|
2699 |
+
overlaySelector = '#qtip-overlay',
|
2700 |
+
globalNamespace = '.qtipmodal',
|
2701 |
+
namespace = globalNamespace + api.id,
|
2702 |
+
attr = 'is-modal-qtip',
|
2703 |
+
docBody = $(document.body),
|
2704 |
+
focusableSelector = PLUGINS.modal.focusable.join(','),
|
2705 |
+
focusableElems = {}, overlay;
|
2706 |
+
|
2707 |
+
// Setup option set checks
|
2708 |
+
api.checks.modal = {
|
2709 |
+
'^show.modal.(on|blur)$': function() {
|
2710 |
+
// Initialise
|
2711 |
+
self.init();
|
2712 |
+
|
2713 |
+
// Show the modal if not visible already and tooltip is visible
|
2714 |
+
elems.overlay.toggle( tooltip.is(':visible') );
|
2715 |
+
},
|
2716 |
+
'^content.text$': function() {
|
2717 |
+
updateFocusable();
|
2718 |
+
}
|
2719 |
+
};
|
2720 |
+
|
2721 |
+
function updateFocusable() {
|
2722 |
+
focusableElems = $(focusableSelector, tooltip).not('[disabled]').map(function() {
|
2723 |
+
return typeof this.focus === 'function' ? this : null;
|
2724 |
+
});
|
2725 |
+
}
|
2726 |
+
|
2727 |
+
function focusInputs(blurElems) {
|
2728 |
+
// Blurring body element in IE causes window.open windows to unfocus!
|
2729 |
+
if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }
|
2730 |
+
|
2731 |
+
// Focus the inputs
|
2732 |
+
else { focusableElems.first().focus(); }
|
2733 |
+
}
|
2734 |
+
|
2735 |
+
function stealFocus(event) {
|
2736 |
+
var target = $(event.target),
|
2737 |
+
container = target.closest('.qtip'),
|
2738 |
+
targetOnTop;
|
2739 |
+
|
2740 |
+
// Determine if input container target is above this
|
2741 |
+
targetOnTop = container.length < 1 ? FALSE :
|
2742 |
+
(parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));
|
2743 |
+
|
2744 |
+
// If we're showing a modal, but focus has landed on an input below
|
2745 |
+
// this modal, divert focus to the first visible input in this modal
|
2746 |
+
// or if we can't find one... the tooltip itself
|
2747 |
+
if(!targetOnTop && ($(event.target).closest(selector)[0] !== tooltip[0])) {
|
2748 |
+
focusInputs(target);
|
2749 |
+
}
|
2750 |
+
}
|
2751 |
+
|
2752 |
+
$.extend(self, {
|
2753 |
+
init: function()
|
2754 |
+
{
|
2755 |
+
// If modal is disabled... return
|
2756 |
+
if(!options.on) { return self; }
|
2757 |
+
|
2758 |
+
// Create the overlay if needed
|
2759 |
+
overlay = self.create();
|
2760 |
+
|
2761 |
+
// Add unique attribute so we can grab modal tooltips easily via a selector
|
2762 |
+
tooltip.attr(attr, TRUE)
|
2763 |
+
|
2764 |
+
// Set z-index
|
2765 |
+
.css('z-index', PLUGINS.modal.zindex + $(selector+'['+attr+']').length)
|
2766 |
+
|
2767 |
+
// Remove previous bound events in globalNamespace
|
2768 |
+
.unbind(globalNamespace).unbind(namespace)
|
2769 |
+
|
2770 |
+
// Apply our show/hide/focus modal events
|
2771 |
+
.bind('tooltipshow'+globalNamespace+' tooltiphide'+globalNamespace, function(event, api, duration) {
|
2772 |
+
var oEvent = event.originalEvent;
|
2773 |
+
|
2774 |
+
// Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
|
2775 |
+
if(event.target === tooltip[0]) {
|
2776 |
+
if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(overlay[0]).length) {
|
2777 |
+
try { event.preventDefault(); } catch(e) {}
|
2778 |
+
}
|
2779 |
+
else if(!oEvent || (oEvent && !oEvent.solo)) {
|
2780 |
+
self[ event.type.replace('tooltip', '') ](event, duration);
|
2781 |
+
}
|
2782 |
+
}
|
2783 |
+
})
|
2784 |
+
|
2785 |
+
// Adjust modal z-index on tooltip focus
|
2786 |
+
.bind('tooltipfocus'+globalNamespace, function(event) {
|
2787 |
+
// If focus was cancelled before it reearch us, don't do anything
|
2788 |
+
if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }
|
2789 |
+
|
2790 |
+
var qtips = $(selector).filter('['+attr+']'),
|
2791 |
+
|
2792 |
+
// Keep the modal's lower than other, regular qtips
|
2793 |
+
newIndex = PLUGINS.modal.zindex + qtips.length,
|
2794 |
+
curIndex = parseInt(tooltip[0].style.zIndex, 10);
|
2795 |
+
|
2796 |
+
// Set overlay z-index
|
2797 |
+
overlay[0].style.zIndex = newIndex - 2;
|
2798 |
+
|
2799 |
+
// Reduce modal z-index's and keep them properly ordered
|
2800 |
+
qtips.each(function() {
|
2801 |
+
if(this.style.zIndex > curIndex) {
|
2802 |
+
this.style.zIndex -= 1;
|
2803 |
+
}
|
2804 |
+
});
|
2805 |
+
|
2806 |
+
// Fire blur event for focused tooltip
|
2807 |
+
qtips.end().filter('.' + focusClass).qtip('blur', event.originalEvent);
|
2808 |
+
|
2809 |
+
// Set the new z-index
|
2810 |
+
tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
|
2811 |
+
|
2812 |
+
// Prevent default handling
|
2813 |
+
try { event.preventDefault(); } catch(e) {}
|
2814 |
+
})
|
2815 |
+
|
2816 |
+
// Focus any other visible modals when this one hides
|
2817 |
+
.bind('tooltiphide'+globalNamespace, function(event) {
|
2818 |
+
if(event.target === tooltip[0]) {
|
2819 |
+
$('[' + attr + ']').filter(':visible').not(tooltip).last().qtip('focus', event);
|
2820 |
+
}
|
2821 |
+
});
|
2822 |
+
|
2823 |
+
// Apply keyboard "Escape key" close handler
|
2824 |
+
if(options.escape) {
|
2825 |
+
$(document).unbind(namespace).bind('keydown'+namespace, function(event) {
|
2826 |
+
if(event.keyCode === 27 && tooltip.hasClass(focusClass)) {
|
2827 |
+
api.hide(event);
|
2828 |
+
}
|
2829 |
+
});
|
2830 |
+
}
|
2831 |
+
|
2832 |
+
// Apply click handler for blur option
|
2833 |
+
if(options.blur) {
|
2834 |
+
elems.overlay.unbind(namespace).bind('click'+namespace, function(event) {
|
2835 |
+
if(tooltip.hasClass(focusClass)) { api.hide(event); }
|
2836 |
+
});
|
2837 |
+
}
|
2838 |
+
|
2839 |
+
// Update focusable elements
|
2840 |
+
updateFocusable();
|
2841 |
+
|
2842 |
+
return self;
|
2843 |
+
},
|
2844 |
+
|
2845 |
+
create: function()
|
2846 |
+
{
|
2847 |
+
var elem = $(overlaySelector);
|
2848 |
+
|
2849 |
+
// Return if overlay is already rendered
|
2850 |
+
if(elem.length) {
|
2851 |
+
// Modal overlay should always be below all tooltips if possible
|
2852 |
+
return (elems.overlay = elem.insertAfter( $(selector).last() ));
|
2853 |
+
}
|
2854 |
+
|
2855 |
+
// Create document overlay
|
2856 |
+
overlay = elems.overlay = $('<div />', {
|
2857 |
+
id: overlaySelector.substr(1),
|
2858 |
+
html: '<div></div>',
|
2859 |
+
mousedown: function() { return FALSE; }
|
2860 |
+
})
|
2861 |
+
.hide()
|
2862 |
+
.insertAfter( $(selector).last() );
|
2863 |
+
|
2864 |
+
// Update position on window resize or scroll
|
2865 |
+
function resize() {
|
2866 |
+
overlay.css({
|
2867 |
+
height: $(window).height(),
|
2868 |
+
width: $(window).width()
|
2869 |
+
});
|
2870 |
+
}
|
2871 |
+
$(window).unbind(globalNamespace).bind('resize'+globalNamespace, resize);
|
2872 |
+
resize(); // Fire it initially too
|
2873 |
+
|
2874 |
+
return overlay;
|
2875 |
+
},
|
2876 |
+
|
2877 |
+
toggle: function(event, state, duration)
|
2878 |
+
{
|
2879 |
+
// Make sure default event hasn't been prevented
|
2880 |
+
if(event && event.isDefaultPrevented()) { return self; }
|
2881 |
+
|
2882 |
+
var effect = options.effect,
|
2883 |
+
type = state ? 'show': 'hide',
|
2884 |
+
visible = overlay.is(':visible'),
|
2885 |
+
modals = $('[' + attr + ']').filter(':visible').not(tooltip),
|
2886 |
+
zindex;
|
2887 |
+
|
2888 |
+
// Create our overlay if it isn't present already
|
2889 |
+
if(!overlay) { overlay = self.create(); }
|
2890 |
+
|
2891 |
+
// Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
|
2892 |
+
if((overlay.is(':animated') && visible === state) || (!state && modals.length)) { return self; }
|
2893 |
+
|
2894 |
+
// State specific...
|
2895 |
+
if(state) {
|
2896 |
+
// Set position
|
2897 |
+
overlay.css({ left: 0, top: 0 });
|
2898 |
+
|
2899 |
+
// Toggle backdrop cursor style on show
|
2900 |
+
overlay.toggleClass('blurs', options.blur);
|
2901 |
+
|
2902 |
+
// IF the modal can steal the focus
|
2903 |
+
if(options.stealfocus !== FALSE) {
|
2904 |
+
// Make sure we can't focus anything outside the tooltip
|
2905 |
+
docBody.bind('focusin'+namespace, stealFocus);
|
2906 |
+
|
2907 |
+
// Blur the current item and focus anything in the modal we an
|
2908 |
+
focusInputs( $('body :focus') );
|
2909 |
+
}
|
2910 |
+
}
|
2911 |
+
else {
|
2912 |
+
// Undelegate focus handler
|
2913 |
+
docBody.unbind('focusin'+namespace);
|
2914 |
+
}
|
2915 |
+
|
2916 |
+
// Stop all animations
|
2917 |
+
overlay.stop(TRUE, FALSE);
|
2918 |
+
|
2919 |
+
// Use custom function if provided
|
2920 |
+
if($.isFunction(effect)) {
|
2921 |
+
effect.call(overlay, state);
|
2922 |
+
}
|
2923 |
+
|
2924 |
+
// If no effect type is supplied, use a simple toggle
|
2925 |
+
else if(effect === FALSE) {
|
2926 |
+
overlay[ type ]();
|
2927 |
+
}
|
2928 |
+
|
2929 |
+
// Use basic fade function
|
2930 |
+
else {
|
2931 |
+
overlay.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
|
2932 |
+
if(!state) { $(this).hide(); }
|
2933 |
+
});
|
2934 |
+
}
|
2935 |
+
|
2936 |
+
// Reset position on hide
|
2937 |
+
if(!state) {
|
2938 |
+
overlay.queue(function(next) {
|
2939 |
+
overlay.css({ left: '', top: '' });
|
2940 |
+
next();
|
2941 |
+
});
|
2942 |
+
}
|
2943 |
+
|
2944 |
+
return self;
|
2945 |
+
},
|
2946 |
+
|
2947 |
+
show: function(event, duration) { return self.toggle(event, TRUE, duration); },
|
2948 |
+
hide: function(event, duration) { return self.toggle(event, FALSE, duration); },
|
2949 |
+
|
2950 |
+
destroy: function()
|
2951 |
+
{
|
2952 |
+
var delBlanket = overlay;
|
2953 |
+
|
2954 |
+
if(delBlanket) {
|
2955 |
+
// Check if any other modal tooltips are present
|
2956 |
+
delBlanket = $('[' + attr + ']').not(tooltip).length < 1;
|
2957 |
+
|
2958 |
+
// Remove overlay if needed
|
2959 |
+
if(delBlanket) {
|
2960 |
+
elems.overlay.remove();
|
2961 |
+
$(document).unbind(globalNamespace);
|
2962 |
+
}
|
2963 |
+
else {
|
2964 |
+
elems.overlay.unbind(globalNamespace+api.id);
|
2965 |
+
}
|
2966 |
+
|
2967 |
+
// Undelegate focus handler
|
2968 |
+
docBody.undelegate('*', 'focusin'+namespace);
|
2969 |
+
}
|
2970 |
+
|
2971 |
+
// Remove bound events
|
2972 |
+
return tooltip.removeAttr(attr).unbind(globalNamespace);
|
2973 |
+
}
|
2974 |
+
});
|
2975 |
+
|
2976 |
+
self.init();
|
2977 |
+
}
|
2978 |
+
|
2979 |
+
PLUGINS.modal = function(api) {
|
2980 |
+
var self = api.plugins.modal;
|
2981 |
+
|
2982 |
+
return 'object' === typeof self ? self : (api.plugins.modal = new Modal(api));
|
2983 |
+
};
|
2984 |
+
|
2985 |
+
// Plugin needs to be initialized on render
|
2986 |
+
PLUGINS.modal.initialize = 'render';
|
2987 |
+
|
2988 |
+
// Setup sanitiztion rules
|
2989 |
+
PLUGINS.modal.sanitize = function(opts) {
|
2990 |
+
if(opts.show) {
|
2991 |
+
if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
|
2992 |
+
else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
|
2993 |
+
}
|
2994 |
+
};
|
2995 |
+
|
2996 |
+
// Base z-index for all modal tooltips (use qTip core z-index as a base)
|
2997 |
+
PLUGINS.modal.zindex = QTIP.zindex - 200;
|
2998 |
+
|
2999 |
+
// Defines the selector used to select all 'focusable' elements within the modal when using the show.modal.stealfocus option.
|
3000 |
+
// Selectors initially taken from http://stackoverflow.com/questions/7668525/is-there-a-jquery-selector-to-get-all-elements-that-can-get-focus
|
3001 |
+
PLUGINS.modal.focusable = ['a[href]', 'area[href]', 'input', 'select', 'textarea', 'button', 'iframe', 'object', 'embed', '[tabindex]', '[contenteditable]'];
|
3002 |
+
|
3003 |
+
// Extend original api defaults
|
3004 |
+
$.extend(TRUE, QTIP.defaults, {
|
3005 |
+
show: {
|
3006 |
+
modal: {
|
3007 |
+
on: FALSE,
|
3008 |
+
effect: TRUE,
|
3009 |
+
blur: TRUE,
|
3010 |
+
stealfocus: TRUE,
|
3011 |
+
escape: TRUE
|
3012 |
+
}
|
3013 |
+
}
|
3014 |
+
});
|
3015 |
+
|
3016 |
+
|
3017 |
+
PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
|
3018 |
+
{
|
3019 |
+
var target = posOptions.target,
|
3020 |
+
tooltip = api.elements.tooltip,
|
3021 |
+
my = posOptions.my,
|
3022 |
+
at = posOptions.at,
|
3023 |
+
adjust = posOptions.adjust,
|
3024 |
+
method = adjust.method.split(' '),
|
3025 |
+
methodX = method[0],
|
3026 |
+
methodY = method[1] || method[0],
|
3027 |
+
viewport = posOptions.viewport,
|
3028 |
+
container = posOptions.container,
|
3029 |
+
cache = api.cache,
|
3030 |
+
tip = api.plugins.tip,
|
3031 |
+
adjusted = { left: 0, top: 0 },
|
3032 |
+
fixed, newMy, newClass;
|
3033 |
+
|
3034 |
+
// If viewport is not a jQuery element, or it's the window/document or no adjustment method is used... return
|
3035 |
+
if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
|
3036 |
+
return adjusted;
|
3037 |
+
}
|
3038 |
+
|
3039 |
+
// Cache our viewport details
|
3040 |
+
fixed = tooltip.css('position') === 'fixed';
|
3041 |
+
viewport = {
|
3042 |
+
elem: viewport,
|
3043 |
+
height: viewport[ (viewport[0] === window ? 'h' : 'outerH') + 'eight' ](),
|
3044 |
+
width: viewport[ (viewport[0] === window ? 'w' : 'outerW') + 'idth' ](),
|
3045 |
+
scrollleft: fixed ? 0 : viewport.scrollLeft(),
|
3046 |
+
scrolltop: fixed ? 0 : viewport.scrollTop(),
|
3047 |
+
offset: viewport.offset() || { left: 0, top: 0 }
|
3048 |
+
};
|
3049 |
+
container = {
|
3050 |
+
elem: container,
|
3051 |
+
scrollLeft: container.scrollLeft(),
|
3052 |
+
scrollTop: container.scrollTop(),
|
3053 |
+
offset: container.offset() || { left: 0, top: 0 }
|
3054 |
+
};
|
3055 |
+
|
3056 |
+
// Generic calculation method
|
3057 |
+
function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {
|
3058 |
+
var initialPos = position[side1],
|
3059 |
+
mySide = my[side], atSide = at[side],
|
3060 |
+
isShift = type === SHIFT,
|
3061 |
+
viewportScroll = -container.offset[side1] + viewport.offset[side1] + viewport['scroll'+side1],
|
3062 |
+
myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
|
3063 |
+
atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
|
3064 |
+
tipLength = tip && tip.size ? tip.size[lengthName] || 0 : 0,
|
3065 |
+
tipAdjust = tip && tip.corner && tip.corner.precedance === side && !isShift ? tipLength : 0,
|
3066 |
+
overflow1 = viewportScroll - initialPos + tipAdjust,
|
3067 |
+
overflow2 = initialPos + elemLength - viewport[lengthName] - viewportScroll + tipAdjust,
|
3068 |
+
offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);
|
3069 |
+
|
3070 |
+
// shift
|
3071 |
+
if(isShift) {
|
3072 |
+
tipAdjust = tip && tip.corner && tip.corner.precedance === otherSide ? tipLength : 0;
|
3073 |
+
offset = (mySide === side1 ? 1 : -1) * myLength - tipAdjust;
|
3074 |
+
|
3075 |
+
// Adjust position but keep it within viewport dimensions
|
3076 |
+
position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
|
3077 |
+
position[side1] = Math.max(
|
3078 |
+
-container.offset[side1] + viewport.offset[side1] + (tipAdjust && tip.corner[side] === CENTER ? tip.offset : 0),
|
3079 |
+
initialPos - offset,
|
3080 |
+
Math.min(
|
3081 |
+
Math.max(-container.offset[side1] + viewport.offset[side1] + viewport[lengthName], initialPos + offset),
|
3082 |
+
position[side1]
|
3083 |
+
)
|
3084 |
+
);
|
3085 |
+
}
|
3086 |
+
|
3087 |
+
// flip/flipinvert
|
3088 |
+
else {
|
3089 |
+
// Update adjustment amount depending on if using flipinvert or flip
|
3090 |
+
adjust *= (type === FLIPINVERT ? 2 : 0);
|
3091 |
+
|
3092 |
+
// Check for overflow on the left/top
|
3093 |
+
if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
|
3094 |
+
position[side1] -= offset + adjust;
|
3095 |
+
newMy['invert'+side](side1);
|
3096 |
+
}
|
3097 |
+
|
3098 |
+
// Check for overflow on the bottom/right
|
3099 |
+
else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) {
|
3100 |
+
position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
|
3101 |
+
newMy['invert'+side](side2);
|
3102 |
+
}
|
3103 |
+
|
3104 |
+
// Make sure we haven't made things worse with the adjustment and reset if so
|
3105 |
+
if(position[side1] < viewportScroll && -position[side1] > overflow2) {
|
3106 |
+
position[side1] = initialPos; newMy = my.clone();
|
3107 |
+
}
|
3108 |
+
}
|
3109 |
+
|
3110 |
+
return position[side1] - initialPos;
|
3111 |
+
}
|
3112 |
+
|
3113 |
+
// Set newMy if using flip or flipinvert methods
|
3114 |
+
if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }
|
3115 |
+
|
3116 |
+
// Adjust position based onviewport and adjustment options
|
3117 |
+
adjusted = {
|
3118 |
+
left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
|
3119 |
+
top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0
|
3120 |
+
};
|
3121 |
+
|
3122 |
+
// Set tooltip position class if it's changed
|
3123 |
+
if(newMy && cache.lastClass !== (newClass = uitooltip + '-pos-' + newMy.abbrev())) {
|
3124 |
+
tooltip.removeClass(api.cache.lastClass).addClass( (api.cache.lastClass = newClass) );
|
3125 |
+
}
|
3126 |
+
|
3127 |
+
return adjusted;
|
3128 |
+
};
|
3129 |
+
PLUGINS.imagemap = function(api, area, corner, adjustMethod)
|
3130 |
+
{
|
3131 |
+
if(!area.jquery) { area = $(area); }
|
3132 |
+
|
3133 |
+
var cache = (api.cache.areas = {}),
|
3134 |
+
shape = (area[0].shape || area.attr('shape')).toLowerCase(),
|
3135 |
+
coordsString = area[0].coords || area.attr('coords'),
|
3136 |
+
baseCoords = coordsString.split(','),
|
3137 |
+
coords = [],
|
3138 |
+
image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
|
3139 |
+
imageOffset = image.offset(),
|
3140 |
+
result = {
|
3141 |
+
width: 0, height: 0,
|
3142 |
+
position: {
|
3143 |
+
top: 1e10, right: 0,
|
3144 |
+
bottom: 0, left: 1e10
|
3145 |
+
}
|
3146 |
+
},
|
3147 |
+
i = 0, next = 0, dimensions;
|
3148 |
+
|
3149 |
+
// POLY area coordinate calculator
|
3150 |
+
// Special thanks to Ed Cradock for helping out with this.
|
3151 |
+
// Uses a binary search algorithm to find suitable coordinates.
|
3152 |
+
function polyCoordinates(result, coords, corner)
|
3153 |
+
{
|
3154 |
+
var i = 0,
|
3155 |
+
compareX = 1, compareY = 1,
|
3156 |
+
realX = 0, realY = 0,
|
3157 |
+
newWidth = result.width,
|
3158 |
+
newHeight = result.height;
|
3159 |
+
|
3160 |
+
// Use a binary search algorithm to locate most suitable coordinate (hopefully)
|
3161 |
+
while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
|
3162 |
+
{
|
3163 |
+
newWidth = Math.floor(newWidth / 2);
|
3164 |
+
newHeight = Math.floor(newHeight / 2);
|
3165 |
+
|
3166 |
+
if(corner.x === LEFT){ compareX = newWidth; }
|
3167 |
+
else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
|
3168 |
+
else{ compareX += Math.floor(newWidth / 2); }
|
3169 |
+
|
3170 |
+
if(corner.y === TOP){ compareY = newHeight; }
|
3171 |
+
else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
|
3172 |
+
else{ compareY += Math.floor(newHeight / 2); }
|
3173 |
+
|
3174 |
+
i = coords.length; while(i--)
|
3175 |
+
{
|
3176 |
+
if(coords.length < 2){ break; }
|
3177 |
+
|
3178 |
+
realX = coords[i][0] - result.position.left;
|
3179 |
+
realY = coords[i][1] - result.position.top;
|
3180 |
+
|
3181 |
+
if((corner.x === LEFT && realX >= compareX) ||
|
3182 |
+
(corner.x === RIGHT && realX <= compareX) ||
|
3183 |
+
(corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) ||
|
3184 |
+
(corner.y === TOP && realY >= compareY) ||
|
3185 |
+
(corner.y === BOTTOM && realY <= compareY) ||
|
3186 |
+
(corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) {
|
3187 |
+
coords.splice(i, 1);
|
3188 |
+
}
|
3189 |
+
}
|
3190 |
+
}
|
3191 |
+
|
3192 |
+
return { left: coords[0][0], top: coords[0][1] };
|
3193 |
+
}
|
3194 |
+
|
3195 |
+
// Make sure we account for padding and borders on the image
|
3196 |
+
imageOffset.left += Math.ceil((image.outerWidth() - image.width()) / 2);
|
3197 |
+
imageOffset.top += Math.ceil((image.outerHeight() - image.height()) / 2);
|
3198 |
+
|
3199 |
+
// Parse coordinates into proper array
|
3200 |
+
if(shape === 'poly') {
|
3201 |
+
i = baseCoords.length; while(i--)
|
3202 |
+
{
|
3203 |
+
next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
|
3204 |
+
|
3205 |
+
if(next[0] > result.position.right){ result.position.right = next[0]; }
|
3206 |
+
if(next[0] < result.position.left){ result.position.left = next[0]; }
|
3207 |
+
if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
|
3208 |
+
if(next[1] < result.position.top){ result.position.top = next[1]; }
|
3209 |
+
|
3210 |
+
coords.push(next);
|
3211 |
+
}
|
3212 |
+
}
|
3213 |
+
else {
|
3214 |
+
i = -1; while(i++ < baseCoords.length) {
|
3215 |
+
coords.push( parseInt(baseCoords[i], 10) );
|
3216 |
+
}
|
3217 |
+
}
|
3218 |
+
|
3219 |
+
// Calculate details
|
3220 |
+
switch(shape)
|
3221 |
+
{
|
3222 |
+
case 'rect':
|
3223 |
+
result = {
|
3224 |
+
width: Math.abs(coords[2] - coords[0]),
|
3225 |
+
height: Math.abs(coords[3] - coords[1]),
|
3226 |
+
position: {
|
3227 |
+
left: Math.min(coords[0], coords[2]),
|
3228 |
+
top: Math.min(coords[1], coords[3])
|
3229 |
+
}
|
3230 |
+
};
|
3231 |
+
break;
|
3232 |
+
|
3233 |
+
case 'circle':
|
3234 |
+
result = {
|
3235 |
+
width: coords[2] + 2,
|
3236 |
+
height: coords[2] + 2,
|
3237 |
+
position: { left: coords[0], top: coords[1] }
|
3238 |
+
};
|
3239 |
+
break;
|
3240 |
+
|
3241 |
+
case 'poly':
|
3242 |
+
result.width = Math.abs(result.position.right - result.position.left);
|
3243 |
+
result.height = Math.abs(result.position.bottom - result.position.top);
|
3244 |
+
|
3245 |
+
if(corner.abbrev() === 'c') {
|
3246 |
+
result.position = {
|
3247 |
+
left: result.position.left + (result.width / 2),
|
3248 |
+
top: result.position.top + (result.height / 2)
|
3249 |
+
};
|
3250 |
+
}
|
3251 |
+
else {
|
3252 |
+
// Calculate if we can't find a cached value
|
3253 |
+
if(!cache[corner+coordsString]) {
|
3254 |
+
result.position = polyCoordinates(result, coords.slice(), corner);
|
3255 |
+
|
3256 |
+
// If flip adjustment is enabled, also calculate the closest opposite point
|
3257 |
+
if(adjustMethod && (adjustMethod[0] === 'flip' || adjustMethod[1] === 'flip')) {
|
3258 |
+
result.offset = polyCoordinates(result, coords.slice(), {
|
3259 |
+
x: corner.x === LEFT ? RIGHT : corner.x === RIGHT ? LEFT : CENTER,
|
3260 |
+
y: corner.y === TOP ? BOTTOM : corner.y === BOTTOM ? TOP : CENTER
|
3261 |
+
});
|
3262 |
+
|
3263 |
+
result.offset.left -= result.position.left;
|
3264 |
+
result.offset.top -= result.position.top;
|
3265 |
+
}
|
3266 |
+
|
3267 |
+
// Store the result
|
3268 |
+
cache[corner+coordsString] = result;
|
3269 |
+
}
|
3270 |
+
|
3271 |
+
// Grab the cached result
|
3272 |
+
result = cache[corner+coordsString];
|
3273 |
+
}
|
3274 |
+
|
3275 |
+
result.width = result.height = 0;
|
3276 |
+
break;
|
3277 |
+
}
|
3278 |
+
|
3279 |
+
// Add image position to offset coordinates
|
3280 |
+
result.position.left += imageOffset.left;
|
3281 |
+
result.position.top += imageOffset.top;
|
3282 |
+
|
3283 |
+
return result;
|
3284 |
+
};
|
3285 |
+
|
3286 |
+
|
3287 |
+
/*
|
3288 |
+
* BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
|
3289 |
+
* Special thanks to Brandon Aaron
|
3290 |
+
*/
|
3291 |
+
function BGIFrame(api)
|
3292 |
+
{
|
3293 |
+
var self = this,
|
3294 |
+
elems = api.elements,
|
3295 |
+
tooltip = elems.tooltip,
|
3296 |
+
namespace = '.bgiframe-' + api.id;
|
3297 |
+
|
3298 |
+
$.extend(self, {
|
3299 |
+
init: function()
|
3300 |
+
{
|
3301 |
+
// Create the BGIFrame element
|
3302 |
+
elems.bgiframe = $('<iframe class="ui-tooltip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
|
3303 |
+
' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
|
3304 |
+
'-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>');
|
3305 |
+
|
3306 |
+
// Append the new element to the tooltip
|
3307 |
+
elems.bgiframe.appendTo(tooltip);
|
3308 |
+
|
3309 |
+
// Update BGIFrame on tooltip move
|
3310 |
+
tooltip.bind('tooltipmove'+namespace, self.adjust);
|
3311 |
+
},
|
3312 |
+
|
3313 |
+
adjust: function()
|
3314 |
+
{
|
3315 |
+
var dimensions = api.get('dimensions'), // Determine current tooltip dimensions
|
3316 |
+
plugin = api.plugins.tip,
|
3317 |
+
tip = elems.tip,
|
3318 |
+
tipAdjust, offset;
|
3319 |
+
|
3320 |
+
// Adjust border offset
|
3321 |
+
offset = parseInt(tooltip.css('border-left-width'), 10) || 0;
|
3322 |
+
offset = { left: -offset, top: -offset };
|
3323 |
+
|
3324 |
+
// Adjust for tips plugin
|
3325 |
+
if(plugin && tip) {
|
3326 |
+
tipAdjust = (plugin.corner.precedance === 'x') ? ['width', 'left'] : ['height', 'top'];
|
3327 |
+
offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
|
3328 |
+
}
|
3329 |
+
|
3330 |
+
// Update bgiframe
|
3331 |
+
elems.bgiframe.css(offset).css(dimensions);
|
3332 |
+
},
|
3333 |
+
|
3334 |
+
destroy: function()
|
3335 |
+
{
|
3336 |
+
// Remove iframe
|
3337 |
+
elems.bgiframe.remove();
|
3338 |
+
|
3339 |
+
// Remove bound events
|
3340 |
+
tooltip.unbind(namespace);
|
3341 |
+
}
|
3342 |
+
});
|
3343 |
+
|
3344 |
+
self.init();
|
3345 |
+
}
|
3346 |
+
|
3347 |
+
PLUGINS.bgiframe = function(api)
|
3348 |
+
{
|
3349 |
+
var browser = $.browser,
|
3350 |
+
self = api.plugins.bgiframe;
|
3351 |
+
|
3352 |
+
// Proceed only if the browser is IE6 and offending elements are present
|
3353 |
+
if($('select, object').length < 1 || !(browser.msie && (''+browser.version).charAt(0) === '6')) {
|
3354 |
+
return FALSE;
|
3355 |
+
}
|
3356 |
+
|
3357 |
+
return 'object' === typeof self ? self : (api.plugins.bgiframe = new BGIFrame(api));
|
3358 |
+
};
|
3359 |
+
|
3360 |
+
// Plugin needs to be initialized on render
|
3361 |
+
PLUGINS.bgiframe.initialize = 'render';
|
3362 |
+
|
3363 |
+
|
3364 |
+
}));
|
3365 |
+
}( window, document ));
|
js/jquery.qtip.min.js
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
/*! qTip2 v2.0.0 | http://craigsworks.com/projects/qtip2/ | Licensed MIT, GPL */
|
2 |
+
(function(a,b,c){(function(a){"use strict",typeof define=="function"&&define.amd?define(["jquery"],a):jQuery&&!jQuery.fn.qtip&&a(jQuery)})(function(d){function I(a){var b=function(a){return a===g||"object"!=typeof a},c=function(a){return!d.isFunction(a)&&(!a&&!a.attr||a.length<1||"object"==typeof a&&!a.jquery)};if(!a||"object"!=typeof a)return f;b(a.metadata)&&(a.metadata={type:a.metadata});if("content"in a){if(b(a.content)||a.content.jquery)a.content={text:a.content};c(a.content.text||f)&&(a.content.text=f),"title"in a.content&&(b(a.content.title)&&(a.content.title={text:a.content.title}),c(a.content.title.text||f)&&(a.content.title.text=f))}return"position"in a&&b(a.position)&&(a.position={my:a.position,at:a.position}),"show"in a&&b(a.show)&&(a.show=a.show.jquery?{target:a.show}:{event:a.show}),"hide"in a&&b(a.hide)&&(a.hide=a.hide.jquery?{target:a.hide}:{event:a.hide}),"style"in a&&b(a.style)&&(a.style={classes:a.style}),d.each(u,function(){this.sanitize&&this.sanitize(a)}),a}function J(h,i,q,r){function Q(a){var b=0,c,d=i,e=a.split(".");while(d=d[e[b++]])b<e.length&&(c=d);return[c||i,e.pop()]}function R(a,b,c){var e=d.Event("tooltip"+a);return e.originalEvent=(c?d.extend({},c):g)||P.event||g,M.trigger(e,[s].concat(b||[])),!e.isDefaultPrevented()}function S(){var a=i.style.widget;M.toggleClass("ui-helper-reset "+y,a).toggleClass(B,i.style.def&&!a),O.content&&O.content.toggleClass(y+"-content",a),O.titlebar&&O.titlebar.toggleClass(y+"-header",a),O.button&&O.button.toggleClass(x+"-icon",!a)}function T(a){O.title&&(O.titlebar.remove(),O.titlebar=O.title=O.button=g,a!==f&&s.reposition())}function U(){var a=i.content.title.button,b=typeof a=="string",c=b?a:"Close tooltip";O.button&&O.button.remove(),a.jquery?O.button=a:O.button=d("<a />",{"class":"ui-state-default ui-tooltip-close "+(i.style.widget?"":x+"-icon"),title:c,"aria-label":c}).prepend(d("<span />",{"class":"ui-icon ui-icon-close",html:"×"})),O.button.appendTo(O.titlebar).attr("role","button").click(function(a){return M.hasClass(z)||s.hide(a),f}),s.redraw()}function V(){var a=J+"-title";O.titlebar&&T(),O.titlebar=d("<div />",{"class":x+"-titlebar "+(i.style.widget?"ui-widget-header":"")}).append(O.title=d("<div />",{id:a,"class":x+"-title","aria-atomic":e})).insertBefore(O.content).delegate(".ui-tooltip-close","mousedown keydown mouseup keyup mouseout",function(a){d(this).toggleClass("ui-state-active ui-state-focus",a.type.substr(-4)==="down")}).delegate(".ui-tooltip-close","mouseover mouseout",function(a){d(this).toggleClass("ui-state-hover",a.type==="mouseover")}),i.content.title.button?U():s.rendered&&s.redraw()}function W(a){var b=O.button,c=O.title;if(!s.rendered)return f;a?(c||V(),U()):b.remove()}function X(a,b){var c=O.title;if(!s.rendered||!a)return f;d.isFunction(a)&&(a=a.call(h,P.event,s));if(a===f||!a&&a!=="")return T(f);a.jquery&&a.length>0?c.empty().append(a.css({display:"block"})):c.html(a),s.redraw(),b!==f&&s.rendered&&M[0].offsetWidth>0&&s.reposition(P.event)}function Y(a,b){function g(a){function i(c){c&&(delete h[c.src],clearTimeout(s.timers.img[c.src]),d(c).unbind(N)),d.isEmptyObject(h)&&(s.redraw(),b!==f&&s.reposition(P.event),a())}var g,h={};if((g=e.find("img[src]:not([height]):not([width])")).length===0)return i();g.each(function(a,b){if(h[b.src]!==c)return;var e=0,f=3;(function g(){if(b.height||b.width||e>f)return i(b);e+=1,s.timers.img[b.src]=setTimeout(g,700)})(),d(b).bind("error"+N+" load"+N,function(){i(this)}),h[b.src]=b})}var e=O.content;return!s.rendered||!a?f:(d.isFunction(a)&&(a=a.call(h,P.event,s)||""),a.jquery&&a.length>0?e.empty().append(a.css({display:"block"})):e.html(a),s.rendered<0?M.queue("fx",g):(L=0,g(d.noop)),s)}function Z(){function l(a){if(M.hasClass(z))return f;clearTimeout(s.timers.show),clearTimeout(s.timers.hide);var b=function(){s.toggle(e,a)};i.show.delay>0?s.timers.show=setTimeout(b,i.show.delay):b()}function m(a){if(M.hasClass(z)||K||L)return f;var b=d(a.relatedTarget||a.target),e=b.closest(A)[0]===M[0],h=b[0]===g.show[0];clearTimeout(s.timers.show),clearTimeout(s.timers.hide);if(c.target==="mouse"&&e||i.hide.fixed&&/mouse(out|leave|move)/.test(a.type)&&(e||h)){try{a.preventDefault(),a.stopImmediatePropagation()}catch(j){}return}i.hide.delay>0?s.timers.hide=setTimeout(function(){s.hide(a)},i.hide.delay):s.hide(a)}function n(a){if(M.hasClass(z))return f;clearTimeout(s.timers.inactive),s.timers.inactive=setTimeout(function(){s.hide(a)},i.hide.inactive)}function o(a){s.rendered&&M[0].offsetWidth>0&&s.reposition(a)}var c=i.position,g={show:i.show.target,hide:i.hide.target,viewport:d(c.viewport),document:d(b),body:d(b.body),window:d(a)},j={show:d.trim(""+i.show.event).split(" "),hide:d.trim(""+i.hide.event).split(" ")},k=d.browser.msie&&parseInt(d.browser.version,10)===6;M.bind("mouseenter"+N+" mouseleave"+N,function(a){var b=a.type==="mouseenter";b&&s.focus(a),M.toggleClass(D,b)}),/mouse(out|leave)/i.test(i.hide.event)&&i.hide.leave==="window"&&g.window.bind("mouseout"+N+" blur"+N,function(a){!/select|option/.test(a.target.nodeName)&&!a.relatedTarget&&s.hide(a)}),i.hide.fixed?(g.hide=g.hide.add(M),M.bind("mouseover"+N,function(){M.hasClass(z)||clearTimeout(s.timers.hide)})):/mouse(over|enter)/i.test(i.show.event)&&g.hide.bind("mouseleave"+N,function(a){clearTimeout(s.timers.show)}),(""+i.hide.event).indexOf("unfocus")>-1&&c.container.closest("html").bind("mousedown"+N,function(a){var b=d(a.target),c=s.rendered&&!M.hasClass(z)&&M[0].offsetWidth>0,e=b.parents(A).filter(M[0]).length>0;b[0]!==h[0]&&b[0]!==M[0]&&!e&&!h.has(b[0]).length&&!b.attr("disabled")&&s.hide(a)}),"number"==typeof i.hide.inactive&&(g.show.bind("qtip-"+q+"-inactive",n),d.each(t.inactiveEvents,function(a,b){g.hide.add(O.tooltip).bind(b+N+"-inactive",n)})),d.each(j.hide,function(a,b){var c=d.inArray(b,j.show),e=d(g.hide);c>-1&&e.add(g.show).length===e.length||b==="unfocus"?(g.show.bind(b+N,function(a){M[0].offsetWidth>0?m(a):l(a)}),delete j.show[c]):g.hide.bind(b+N,m)}),d.each(j.show,function(a,b){g.show.bind(b+N,l)}),"number"==typeof i.hide.distance&&g.show.add(M).bind("mousemove"+N,function(a){var b=P.origin||{},c=i.hide.distance,d=Math.abs;(d(a.pageX-b.pageX)>=c||d(a.pageY-b.pageY)>=c)&&s.hide(a)}),c.target==="mouse"&&(g.show.bind("mousemove"+N,function(a){v={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),c.adjust.mouse&&(i.hide.event&&(M.bind("mouseleave"+N,function(a){(a.relatedTarget||a.target)!==g.show[0]&&s.hide(a)}),O.target.bind("mouseenter"+N+" mouseleave"+N,function(a){P.onTarget=a.type==="mouseenter"})),g.document.bind("mousemove"+N,function(a){s.rendered&&P.onTarget&&!M.hasClass(z)&&M[0].offsetWidth>0&&s.reposition(a||v)}))),(c.adjust.resize||g.viewport.length)&&(d.event.special.resize?g.viewport:g.window).bind("resize"+N,o),(g.viewport.length||k&&M.css("position")==="fixed")&&g.viewport.bind("scroll"+N,o)}function _(){var c=[i.show.target[0],i.hide.target[0],s.rendered&&O.tooltip[0],i.position.container[0],i.position.viewport[0],i.position.container.closest("html")[0],a,b];s.rendered?d([]).pushStack(d.grep(c,function(a){return typeof a=="object"})).unbind(N):i.show.target.unbind(N+"-create")}var s=this,E=b.body,J=x+"-"+q,K=0,L=0,M=d(),N=".qtip-"+q,O,P;s.id=q,s.rendered=f,s.destroyed=f,s.elements=O={target:h},s.timers={img:{}},s.options=i,s.checks={},s.plugins={},s.cache=P={event:{},target:d(),disabled:f,attr:r,onTarget:f,lastClass:""},s.checks.builtin={"^id$":function(a,b,c){var g=c===e?t.nextid:c,h=x+"-"+g;g!==f&&g.length>0&&!d("#"+h).length&&(M[0].id=h,O.content[0].id=h+"-content",O.title[0].id=h+"-title")},"^content.text$":function(a,b,c){Y(c)},"^content.title.text$":function(a,b,c){if(!c)return T();!O.title&&c&&V(),X(c)},"^content.title.button$":function(a,b,c){W(c)},"^position.(my|at)$":function(a,b,c){"string"==typeof c&&(a[b]=new u.Corner(c))},"^position.container$":function(a,b,c){s.rendered&&M.appendTo(c)},"^show.ready$":function(){s.rendered?s.toggle(e):s.render(1)},"^style.classes$":function(a,b,c){M.attr("class",x+" qtip "+c)},"^style.widget|content.title":S,"^events.(render|show|move|hide|focus|blur)$":function(a,b,c){M[(d.isFunction(c)?"":"un")+"bind"]("tooltip"+b,c)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){var a=i.position;M.attr("tracking",a.target==="mouse"&&a.adjust.mouse),_(),Z()}},d.extend(s,{render:function(a){if(s.rendered)return s;var b=i.content.text,c=i.content.title.text,g=i.position;return d.attr(h[0],"aria-describedby",J),M=O.tooltip=d("<div/>",{id:J,"class":x+" qtip "+B+" "+i.style.classes+" "+x+"-pos-"+i.position.my.abbrev(),width:i.style.width||"",height:i.style.height||"",tracking:g.target==="mouse"&&g.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":f,"aria-describedby":J+"-content","aria-hidden":e}).toggleClass(z,P.disabled).data("qtip",s).appendTo(i.position.container).append(O.content=d("<div />",{"class":x+"-content",id:J+"-content","aria-atomic":e})),s.rendered=-1,L=1,K=1,c&&(V(),d.isFunction(c)||X(c,f)),d.isFunction(b)||Y(b,f),s.rendered=e,S(),d.each(i.events,function(a,b){d.isFunction(b)&&M.bind(a==="toggle"?"tooltipshow tooltiphide":"tooltip"+a,b)}),d.each(u,function(){this.initialize==="render"&&this(s)}),Z(),M.queue("fx",function(b){R("render"),L=0,K=0,s.redraw(),(i.show.ready||a)&&s.toggle(e,P.event,f),b()}),s},get:function(a){var b,c;switch(a.toLowerCase()){case"dimensions":b={height:M.outerHeight(),width:M.outerWidth()};break;case"offset":b=u.offset(M,i.position.container);break;default:c=Q(a.toLowerCase()),b=c[0][c[1]],b=b.precedance?b.string():b}return b},set:function(a,b){function n(a,b){var c,d,e;for(c in l)for(d in l[c])if(e=(new RegExp(d,"i")).exec(a))b.push(e),l[c][d].apply(s,b)}var c=/^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,h=/^content\.(title|attr)|style/i,j=f,k=f,l=s.checks,m;return"string"==typeof a?(m=a,a={},a[m]=b):a=d.extend(e,{},a),d.each(a,function(b,e){var f=Q(b.toLowerCase()),g;g=f[0][f[1]],f[0][f[1]]="object"==typeof e&&e.nodeType?d(e):e,a[b]=[f[0],f[1],e,g],j=c.test(b)||j,k=h.test(b)||k}),I(i),K=L=1,d.each(a,n),K=L=0,s.rendered&&M[0].offsetWidth>0&&(j&&s.reposition(i.position.target==="mouse"?g:P.event),k&&s.redraw()),s},toggle:function(a,c){function t(){a?(d.browser.msie&&M[0].style.removeAttribute("filter"),M.css("overflow",""),"string"==typeof h.autofocus&&d(h.autofocus,M).focus(),h.target.trigger("qtip-"+q+"-inactive")):M.css({display:"",visibility:"",opacity:"",left:"",top:""}),R(a?"visible":"hidden")}if(!s.rendered)return a?s.render(1):s;var g=a?"show":"hide",h=i[g],j=i[a?"hide":"show"],k=i.position,l=i.content,m=M[0].offsetWidth>0,n=a||h.target.length===1,o=!c||h.target.length<2||P.target[0]===c.target,p,r;(typeof a).search("boolean|number")&&(a=!m);if(!M.is(":animated")&&m===a&&o)return s;if(c){if(/over|enter/.test(c.type)&&/out|leave/.test(P.event.type)&&i.show.target.add(c.target).length===i.show.target.length&&M.has(c.relatedTarget).length)return s;P.event=d.extend({},c)}return R(g,[90])?(d.attr(M[0],"aria-hidden",!a),a?(P.origin=d.extend({},v),s.focus(c),d.isFunction(l.text)&&Y(l.text,f),d.isFunction(l.title.text)&&X(l.title.text,f),!G&&k.target==="mouse"&&k.adjust.mouse&&(d(b).bind("mousemove.qtip",function(a){v={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),G=e),s.reposition(c,arguments[2]),!h.solo||d(A,h.solo).not(M).qtip("hide",d.Event("tooltipsolo"))):(clearTimeout(s.timers.show),delete P.origin,G&&!d(A+'[tracking="true"]:visible',h.solo).not(M).length&&(d(b).unbind("mousemove.qtip"),G=f),s.blur(c)),h.effect===f||n===f?(M[g](),t.call(M)):d.isFunction(h.effect)?(M.stop(1,1),h.effect.call(M,s),M.queue("fx",function(a){t(),a()})):M.fadeTo(90,a?1:0,t),a&&h.target.trigger("qtip-"+q+"-inactive"),s):s},show:function(a){return s.toggle(e,a)},hide:function(a){return s.toggle(f,a)},focus:function(a){if(!s.rendered)return s;var b=d(A),c=parseInt(M[0].style.zIndex,10),e=t.zindex+b.length,f=d.extend({},a),g;return M.hasClass(C)||R("focus",[e],f)&&(c!==e&&(b.each(function(){this.style.zIndex>c&&(this.style.zIndex=this.style.zIndex-1)}),b.filter("."+C).qtip("blur",f)),M.addClass(C)[0].style.zIndex=e),s},blur:function(a){return M.removeClass(C),R("blur",[M.css("zIndex")],a),s},reposition:function(c,e){if(!s.rendered||K)return s;K=1;var g=i.position.target,h=i.position,j=h.my,k=h.at,q=h.adjust,r=q.method.split(" "),t=M.outerWidth(),w=M.outerHeight(),x=0,y=0,z=M.css("position")==="fixed",A=h.viewport,B={left:0,top:0},C=h.container,D=M[0].offsetWidth>0,E,F,G;if(d.isArray(g)&&g.length===2)k={x:m,y:l},B={left:g[0],top:g[1]};else if(g==="mouse"&&(c&&c.pageX||P.event.pageX))k={x:m,y:l},c=(c&&(c.type==="resize"||c.type==="scroll")?P.event:c&&c.pageX&&c.type==="mousemove"?c:v&&v.pageX&&(q.mouse||!c||!c.pageX)?{pageX:v.pageX,pageY:v.pageY}:!q.mouse&&P.origin&&P.origin.pageX&&i.show.distance?P.origin:c)||c||P.event||v||{},B={top:c.pageY,left:c.pageX};else{g==="event"&&c&&c.target&&c.type!=="scroll"&&c.type!=="resize"?P.target=d(c.target):g!=="event"&&(P.target=d(g.jquery?g:O.target)),g=P.target,g=d(g).eq(0);if(g.length===0)return s;g[0]===b||g[0]===a?(x=u.iOS?a.innerWidth:g.width(),y=u.iOS?a.innerHeight:g.height(),g[0]===a&&(B={top:(A||g).scrollTop(),left:(A||g).scrollLeft()})):u.imagemap&&g.is("area")?E=u.imagemap(s,g,k,u.viewport?r:f):u.svg&&typeof g[0].xmlbase=="string"?E=u.svg(s,g,k,u.viewport?r:f):(x=g.outerWidth(),y=g.outerHeight(),B=u.offset(g,C)),E&&(x=E.width,y=E.height,F=E.offset,B=E.position);if(u.iOS>3.1&&u.iOS<4.1||u.iOS>=4.3&&u.iOS<4.33||!u.iOS&&z)G=d(a),B.left-=G.scrollLeft(),B.top-=G.scrollTop();B.left+=k.x===o?x:k.x===p?x/2:0,B.top+=k.y===n?y:k.y===p?y/2:0}return B.left+=q.x+(j.x===o?-t:j.x===p?-t/2:0),B.top+=q.y+(j.y===n?-w:j.y===p?-w/2:0),u.viewport?(B.adjusted=u.viewport(s,B,h,x,y,t,w),F&&B.adjusted.left&&(B.left+=F.left),F&&B.adjusted.top&&(B.top+=F.top)):B.adjusted={left:0,top:0},R("move",[B,A.elem||A],c)?(delete B.adjusted,e===f||!D||isNaN(B.left)||isNaN(B.top)||g==="mouse"||!d.isFunction(h.effect)?M.css(B):d.isFunction(h.effect)&&(h.effect.call(M,s,d.extend({},B)),M.queue(function(a){d(this).css({opacity:"",height:""}),d.browser.msie&&this.style.removeAttribute("filter"),a()})),K=0,s):s},redraw:function(){if(s.rendered<1||L)return s;var a=i.style,b=i.position.container,c,d,e,f;return L=1,R("redraw"),a.height&&M.css(k,a.height),a.width?M.css(j,a.width):(M.css(j,"").appendTo(H),d=M.width(),d%2<1&&(d+=1),e=M.css("max-width")||"",f=M.css("min-width")||"",c=(e+f).indexOf("%")>-1?b.width()/100:0,e=(e.indexOf("%")>-1?c:1)*parseInt(e,10)||d,f=(f.indexOf("%")>-1?c:1)*parseInt(f,10)||0,d=e+f?Math.min(Math.max(d,f),e):d,M.css(j,Math.round(d)).appendTo(b)),R("redrawn"),L=0,s},disable:function(a){return"boolean"!=typeof a&&(a=!M.hasClass(z)&&!P.disabled),s.rendered?(M.toggleClass(z,a),d.attr(M[0],"aria-disabled",a)):P.disabled=!!a,s},enable:function(){return s.disable(f)},destroy:function(){var a=h[0],b=d.attr(a,F),c=h.data("qtip");s.destroyed=e,s.rendered&&(M.stop(1,0).remove(),d.each(s.plugins,function(){this.destroy&&this.destroy()})),clearTimeout(s.timers.show),clearTimeout(s.timers.hide),_();if(!c||s===c)d.removeData(a,"qtip"),i.suppress&&b&&(d.attr(a,"title",b),h.removeAttr(F)),h.removeAttr("aria-describedby");return h.unbind(".qtip-"+q),delete w[s.id],h}})}function K(a,c){var h,i,j,k,l,m=d(this),n=d(b.body),o=this===b?n:m,p=m.metadata?m.metadata(c.metadata):g,q=c.metadata.type==="html5"&&p?p[c.metadata.name]:g,r=m.data(c.metadata.name||"qtipopts");try{r=typeof r=="string"?d.parseJSON(r):r}catch(s){}k=d.extend(e,{},t.defaults,c,typeof r=="object"?I(r):g,I(q||p)),i=k.position,k.id=a;if("boolean"==typeof k.content.text){j=m.attr(k.content.attr);if(k.content.attr!==f&&j)k.content.text=j;else return f}i.container.length||(i.container=n),i.target===f&&(i.target=o),k.show.target===f&&(k.show.target=o),k.show.solo===e&&(k.show.solo=i.container.closest("body")),k.hide.target===f&&(k.hide.target=o),k.position.viewport===e&&(k.position.viewport=i.container),i.container=i.container.eq(0),i.at=new u.Corner(i.at),i.my=new u.Corner(i.my);if(d.data(this,"qtip"))if(k.overwrite)m.qtip("destroy");else if(k.overwrite===f)return f;return k.suppress&&(l=d.attr(this,"title"))&&d(this).removeAttr("title").attr(F,l).attr("title",""),h=new J(m,k,a,!!j),d.data(this,"qtip",h),m.bind("remove.qtip-"+a+" removeqtip.qtip-"+a,function(){h.destroy()}),h}function L(a){var b=this,c=a.elements.tooltip,g=a.options.content.ajax,h=t.defaults.content.ajax,i=".qtip-ajax",j=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,k=e,l=f,m;a.checks.ajax={"^content.ajax":function(a,d,e){d==="ajax"&&(g=e),d==="once"?b.init():g&&g.url?b.load():c.unbind(i)}},d.extend(b,{init:function(){return g&&g.url&&c.unbind(i)[g.once?"one":"bind"]("tooltipshow"+i,b.load),b},load:function(c){function r(){var b;if(a.destroyed)return;k=f,p&&(l=e,a.show(c.originalEvent)),(b=h.complete||g.complete)&&d.isFunction(b)&&b.apply(g.context||a,arguments)}function s(b,c,e){var f;if(a.destroyed)return;o&&"string"==typeof b&&(b=d("<div/>").append(b.replace(j,"")).find(o)),(f=h.success||g.success)&&d.isFunction(f)?f.call(g.context||a,b,c,e):a.set("content.text",b)}function t(b,c,d){if(a.destroyed||b.status===0)return;a.set("content.text",c+": "+d)}if(l){l=f;return}var i=g.url.lastIndexOf(" "),n=g.url,o,p=!g.loading&&k;if(p)try{c.preventDefault()}catch(q){}else if(c&&c.isDefaultPrevented())return b;m&&m.abort&&m.abort(),i>-1&&(o=n.substr(i),n=n.substr(0,i)),m=d.ajax(d.extend({error:h.error||t,context:a},g,{url:n,success:s,complete:r}))},destroy:function(){m&&m.abort&&m.abort(),a.destroyed=e}}),b.init()}function M(a,b,c){var d=Math.ceil(b/2),e=Math.ceil(c/2),f={bottomright:[[0,0],[b,c],[b,0]],bottomleft:[[0,0],[b,0],[0,c]],topright:[[0,c],[b,0],[b,c]],topleft:[[0,0],[0,c],[b,c]],topcenter:[[0,c],[d,0],[b,c]],bottomcenter:[[0,0],[b,0],[d,c]],rightcenter:[[0,0],[b,e],[0,c]],leftcenter:[[b,0],[b,c],[0,e]]};return f.lefttop=f.bottomright,f.righttop=f.bottomleft,f.leftbottom=f.topright,f.rightbottom=f.topleft,f[a.string()]}function N(a,b){function D(a){var b=v.is(":visible");v.show(),a(),v.toggle(b)}function E(){x.width=r.height,x.height=r.width}function F(){x.width=r.width,x.height=r.height}function G(b,d,g,j){if(!t.tip)return;var k=q.corner.clone(),u=g.adjusted,v=a.options.position.adjust.method.split(" "),x=v[0],y=v[1]||v[0],z={left:f,top:f,x:0,y:0},A,B={},C;q.corner.fixed!==e&&(x===s&&k.precedance===h&&u.left&&k.y!==p?k.precedance=k.precedance===h?i:h:x!==s&&u.left&&(k.x=k.x===p?u.left>0?m:o:k.x===m?o:m),y===s&&k.precedance===i&&u.top&&k.x!==p?k.precedance=k.precedance===i?h:i:y!==s&&u.top&&(k.y=k.y===p?u.top>0?l:n:k.y===l?n:l),k.string()!==w.corner.string()&&(w.top!==u.top||w.left!==u.left)&&q.update(k,f)),A=q.position(k,u),A[k.x]+=I(k,k.x),A[k.y]+=I(k,k.y),A.right!==c&&(A.left=-A.right),A.bottom!==c&&(A.top=-A.bottom),A.user=Math.max(0,r.offset);if(z.left=x===s&&!!u.left)k.x===p?B["margin-left"]=z.x=A["margin-left"]-u.left:(C=A.right!==c?[u.left,-A.left]:[-u.left,A.left],(z.x=Math.max(C[0],C[1]))>C[0]&&(g.left-=u.left,z.left=f),B[A.right!==c?o:m]=z.x);if(z.top=y===s&&!!u.top)k.y===p?B["margin-top"]=z.y=A["margin-top"]-u.top:(C=A.bottom!==c?[u.top,-A.top]:[-u.top,A.top],(z.y=Math.max(C[0],C[1]))>C[0]&&(g.top-=u.top,z.top=f),B[A.bottom!==c?n:l]=z.y);t.tip.css(B).toggle(!(z.x&&z.y||k.x===p&&z.y||k.y===p&&z.x)),g.left-=A.left.charAt?A.user:x!==s||z.top||!z.left&&!z.top?A.left:0,g.top-=A.top.charAt?A.user:y!==s||z.left||!z.left&&!z.top?A.top:0,w.left=u.left,w.top=u.top,w.corner=k.clone()}function H(){var b=r.corner,c=a.options.position,d=c.at,g=c.my.string?c.my.string():c.my;return b===f||g===f&&d===f?f:(b===e?q.corner=new u.Corner(g):b.string||(q.corner=new u.Corner(b),q.corner.fixed=e),w.corner=new u.Corner(q.corner.string()),q.corner.string()!=="centercenter")}function I(a,b,c){b=b?b:a[a.precedance];var d=t.titlebar&&a.y===l,e=d?t.titlebar:v,f="border-"+b+"-width",g=function(a){return parseInt(a.css(f),10)},h;return D(function(){h=(c?g(c):g(t.content)||g(e)||g(v))||0}),h}function J(a){var b=t.titlebar&&a.y===l,c=b?t.titlebar:t.content,e=d.browser.mozilla,f=e?"-moz-":d.browser.webkit?"-webkit-":"",g="border-radius-"+a.y+a.x,h="border-"+a.y+"-"+a.x+"-radius",i=function(a){return parseInt(c.css(a),10)||parseInt(v.css(a),10)},j;return D(function(){j=i(h)||i(f+h)||i(f+g)||i(g)||0}),j}function K(a){function z(a,b,c){var d=a.css(b)||n;return c&&d===a.css(c)?f:j.test(d)?f:d}var b,c,g,h=t.tip.css("cssText",""),i=a||q.corner,j=/rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,k="border-"+i[i.precedance]+"-color",m="background-color",n="transparent",o=" !important",s=t.titlebar,u=s&&(i.y===l||i.y===p&&h.position().top+x.height/2+r.offset<s.outerHeight(e)),w=u?s:t.content;D(function(){y.fill=z(h,m)||z(w,m)||z(t.content,m)||z(v,m)||h.css(m),y.border=z(h,k,"color")||z(w,k,"color")||z(t.content,k,"color")||z(v,k,"color")||v.css(k),d("*",h).add(h).css("cssText",m+":"+n+o+";border:0"+o+";")})}function L(a){var b=a.precedance===i,c=x[b?j:k],d=x[b?k:j],e=a.string().indexOf(p)>-1,f=c*(e?.5:1),g=Math.pow,h=Math.round,l,m,n,o=Math.sqrt(g(f,2)+g(d,2)),q=[z/f*o,z/d*o];return q[2]=Math.sqrt(g(q[0],2)-g(z,2)),q[3]=Math.sqrt(g(q[1],2)-g(z,2)),l=o+q[2]+q[3]+(e?0:q[0]),m=l/o,n=[h(m*d),h(m*c)],{height:n[b?0:1],width:n[b?1:0]}}function N(a,b,c){return"<qvml:"+a+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(b||"")+' style="behavior: url(#default#VML); '+(c||"")+'" />'}var q=this,r=a.options.style.tip,t=a.elements,v=t.tooltip,w={top:0,left:0},x={width:r.width,height:r.height},y={},z=r.border||0,A=".qtip-tip",B=!!(d("<canvas />")[0]||{}).getContext,C;q.corner=g,q.mimic=g,q.border=z,q.offset=r.offset,q.size=x,a.checks.tip={"^position.my|style.tip.(corner|mimic|border)$":function(){q.init()||q.destroy(),a.reposition()},"^style.tip.(height|width)$":function(){x={width:r.width,height:r.height},q.create(),q.update(),a.reposition()},"^content.title.text|style.(classes|widget)$":function(){t.tip&&t.tip.length&&q.update()}},d.extend(q,{init:function(){var a=H()&&(B||d.browser.msie);return a&&(q.create(),q.update(),v.unbind(A).bind("tooltipmove"+A,G),B||v.bind("tooltipredraw tooltipredrawn",function(a){a.type==="tooltipredraw"?(C=t.tip.html(),t.tip.html("")):t.tip.html(C)})),a},create:function(){var a=x.width,b=x.height,c;t.tip&&t.tip.remove(),t.tip=d("<div />",{"class":"ui-tooltip-tip"}).css({width:a,height:b}).prependTo(v),B?d("<canvas />").appendTo(t.tip)[0].getContext("2d").save():(c=N("shape",'coordorigin="0,0"',"position:absolute;"),t.tip.html(c+c),d("*",t.tip).bind("click mousedown",function(a){a.stopPropagation()}))},update:function(a,b){var c=t.tip,j=c.children(),k=x.width,s=x.height,A=r.mimic,C=Math.round,D,G,H,J,O;a||(a=w.corner||q.corner),A===f?A=a:(A=new u.Corner(A),A.precedance=a.precedance,A.x==="inherit"?A.x=a.x:A.y==="inherit"?A.y=a.y:A.x===A.y&&(A[a.precedance]=a[a.precedance])),D=A.precedance,a.precedance===h?E():F(),t.tip.css({width:k=x.width,height:s=x.height}),K(a),y.border!=="transparent"?(z=I(a,g),r.border===0&&z>0&&(y.fill=y.border),q.border=z=r.border!==e?r.border:z):q.border=z=0,H=M(A,k,s),q.size=O=L(a),c.css(O),a.precedance===i?J=[C(A.x===m?z:A.x===o?O.width-k-z:(O.width-k)/2),C(A.y===l?O.height-s:0)]:J=[C(A.x===m?O.width-k:0),C(A.y===l?z:A.y===n?O.height-s-z:(O.height-s)/2)],B?(j.attr(O),G=j[0].getContext("2d"),G.restore(),G.save(),G.clearRect(0,0,3e3,3e3),G.fillStyle=y.fill,G.strokeStyle=y.border,G.lineWidth=z*2,G.lineJoin="miter",G.miterLimit=100,G.translate(J[0],J[1]),G.beginPath(),G.moveTo(H[0][0],H[0][1]),G.lineTo(H[1][0],H[1][1]),G.lineTo(H[2][0],H[2][1]),G.closePath(),z&&(v.css("background-clip")==="border-box"&&(G.strokeStyle=y.fill,G.stroke()),G.strokeStyle=y.border,G.stroke()),G.fill()):(H="m"+H[0][0]+","+H[0][1]+" l"+H[1][0]+","+H[1][1]+" "+H[2][0]+","+H[2][1]+" xe",J[2]=z&&/^(r|b)/i.test(a.string())?parseFloat(d.browser.version,10)===8?2:1:0,j.css({coordsize:k+z+" "+(s+z),antialias:""+(A.string().indexOf(p)>-1),left:J[0],top:J[1],width:k+z,height:s+z}).each(function(a){var b=d(this);b[b.prop?"prop":"attr"]({coordsize:k+z+" "+(s+z),path:H,fillcolor:y.fill,filled:!!a,stroked:!a}).toggle(!!z||!!a),!a&&b.html()===""&&b.html(N("stroke",'weight="'+z*2+'px" color="'+y.border+'" miterlimit="1000" joinstyle="miter"'))})),b!==f&&q.position(a)},position:function(a){var b=t.tip,c={},e=Math.max(0,r.offset),g,n,o;return r.corner===f||!b?f:(a=a||q.corner,g=a.precedance,n=L(a),o=[a.x,a.y],g===h&&o.reverse(),d.each(o,function(b,d){var f,h,o;d===p?(f=g===i?m:l,c[f]="50%",c["margin-"+f]=-Math.round(n[g===i?j:k]/2)+e):(f=I(a,d),h=I(a,d,t.content),o=J(a),c[d]=b?h:e+(o>f?o:-f))}),c[a[g]]-=n[g===h?j:k],b.css({top:"",bottom:"",left:"",right:"",margin:""}).css(c),c)},destroy:function(){t.tip&&t.tip.remove(),t.tip=!1,v.unbind(A)}}),q.init()}function O(c){function s(){q=d(p,j).not("[disabled]").map(function(){return typeof this.focus=="function"?this:null})}function t(a){q.length<1&&a.length?a.not("body").blur():q.first().focus()}function v(a){var b=d(a.target),c=b.closest(".qtip"),e;e=c.length<1?f:parseInt(c[0].style.zIndex,10)>parseInt(j[0].style.zIndex,10),!e&&d(a.target).closest(A)[0]!==j[0]&&t(b)}var g=this,h=c.options.show.modal,i=c.elements,j=i.tooltip,k="#qtip-overlay",l=".qtipmodal",m=l+c.id,n="is-modal-qtip",o=d(b.body),p=u.modal.focusable.join(","),q={},r;c.checks.modal={"^show.modal.(on|blur)$":function(){g.init(),i.overlay.toggle(j.is(":visible"))},"^content.text$":function(){s()}},d.extend(g,{init:function(){return h.on?(r=g.create(),j.attr(n,e).css("z-index",u.modal.zindex+d(A+"["+n+"]").length).unbind(l).unbind(m).bind("tooltipshow"+l+" tooltiphide"+l,function(a,b,c){var e=a.originalEvent;if(a.target===j[0])if(e&&a.type==="tooltiphide"&&/mouse(leave|enter)/.test(e.type)&&d(e.relatedTarget).closest(r[0]).length)try{a.preventDefault()}catch(f){}else(!e||e&&!e.solo)&&g[a.type.replace("tooltip","")](a,c)}).bind("tooltipfocus"+l,function(a){if(a.isDefaultPrevented()||a.target!==j[0])return;var b=d(A).filter("["+n+"]"),c=u.modal.zindex+b.length,e=parseInt(j[0].style.zIndex,10);r[0].style.zIndex=c-2,b.each(function(){this.style.zIndex>e&&(this.style.zIndex-=1)}),b.end().filter("."+C).qtip("blur",a.originalEvent),j.addClass(C)[0].style.zIndex=c;try{a.preventDefault()}catch(f){}}).bind("tooltiphide"+l,function(a){a.target===j[0]&&d("["+n+"]").filter(":visible").not(j).last().qtip("focus",a)}),h.escape&&d(b).unbind(m).bind("keydown"+m,function(a){a.keyCode===27&&j.hasClass(C)&&c.hide(a)}),h.blur&&i.overlay.unbind(m).bind("click"+m,function(a){j.hasClass(C)&&c.hide(a)}),s(),g):g},create:function(){function c(){r.css({height:d(a).height(),width:d(a).width()})}var b=d(k);return b.length?i.overlay=b.insertAfter(d(A).last()):(r=i.overlay=d("<div />",{id:k.substr(1),html:"<div></div>",mousedown:function(){return f}}).hide().insertAfter(d(A).last()),d(a).unbind(l).bind("resize"+l,c),c(),r)},toggle:function(a,b,c){if(a&&a.isDefaultPrevented())return g;var i=h.effect,k=b?"show":"hide",l=r.is(":visible"),p=d("["+n+"]").filter(":visible").not(j),q;return r||(r=g.create()),r.is(":animated")&&l===b||!b&&p.length?g:(b?(r.css({left:0,top:0}),r.toggleClass("blurs",h.blur),h.stealfocus!==f&&(o.bind("focusin"+m,v),t(d("body :focus")))):o.unbind("focusin"+m),r.stop(e,f),d.isFunction(i)?i.call(r,b):i===f?r[k]():r.fadeTo(parseInt(c,10)||90,b?1:0,function(){b||d(this).hide()}),b||r.queue(function(a){r.css({left:"",top:""}),a()}),g)},show:function(a,b){return g.toggle(a,e,b)},hide:function(a,b){return g.toggle(a,f,b)},destroy:function(){var a=r;return a&&(a=d("["+n+"]").not(j).length<1,a?(i.overlay.remove(),d(b).unbind(l)):i.overlay.unbind(l+c.id),o.undelegate("*","focusin"+m)),j.removeAttr(n).unbind(l)}}),g.init()}function P(a){var b=this,c=a.elements,e=c.tooltip,f=".bgiframe-"+a.id;d.extend(b,{init:function(){c.bgiframe=d('<iframe class="ui-tooltip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>'),c.bgiframe.appendTo(e),e.bind("tooltipmove"+f,b.adjust)},adjust:function(){var b=a.get("dimensions"),d=a.plugins.tip,f=c.tip,g,h;h=parseInt(e.css("border-left-width"),10)||0,h={left:-h,top:-h},d&&f&&(g=d.corner.precedance==="x"?["width","left"]:["height","top"],h[g[1]]-=f[g[0]]()),c.bgiframe.css(h).css(b)},destroy:function(){c.bgiframe.remove(),e.unbind(f)}}),b.init()}var e=!0,f=!1,g=null,h="x",i="y",j="width",k="height",l="top",m="left",n="bottom",o="right",p="center",q="flip",r="flipinvert",s="shift",t,u,v,w={},x="ui-tooltip",y="ui-widget",z="ui-state-disabled",A="div.qtip."+x,B=x+"-default",C=x+"-focus",D=x+"-hover",E="_replacedByqTip",F="oldtitle",G,H;H=d("<div/>",{id:"qtip-rcontainer"}),d(function(){H.appendTo(b.body)}),t=d.fn.qtip=function(a,b,h){var i=(""+a).toLowerCase(),j=g,k=d.makeArray(arguments).slice(1),l=k[k.length-1],m=this[0]?d.data(this[0],"qtip"):g;if(!arguments.length&&m||i==="api")return m;if("string"==typeof a)return this.each(function(){var a=d.data(this,"qtip");if(!a)return e;l&&l.timeStamp&&(a.cache.event=l);if(i!=="option"&&i!=="options"||!b)a[i]&&a[i].apply(a[i],k);else if(d.isPlainObject(b)||h!==c)a.set(b,h);else return j=a.get(b),f}),j!==g?j:this;if("object"==typeof a||!arguments.length)return m=I(d.extend(e,{},a)),t.bind.call(this,m,l)},t.bind=function(a,b){return this.each(function(g){function n(a){function b(){l.render(typeof a=="object"||h.show.ready),i.show.add(i.hide).unbind(k)}if(l.cache.disabled)return f;l.cache.event=d.extend({},a),l.cache.target=a?d(a.target):[c],h.show.delay>0?(clearTimeout(l.timers.show),l.timers.show=setTimeout(b,h.show.delay),j.show!==j.hide&&i.hide.bind(j.hide,function(){clearTimeout(l.timers.show)})):b()}var h,i,j,k,l,m;m=d.isArray(a.id)?a.id[g]:a.id,m=!m||m===f||m.length<1||w[m]?t.nextid++:w[m]=m,k=".qtip-"+m+"-create",l=K.call(this,m,a);if(l===f)return e;h=l.options,d.each(u,function(){this.initialize==="initialize"&&this(l)}),i={show:h.show.target,hide:h.hide.target},j={show:d.trim(""+h.show.event).replace(/ /g,k+" ")+k,hide:d.trim(""+h.hide.event).replace(/ /g,k+" ")+k},/mouse(over|enter)/i.test(j.show)&&!/mouse(out|leave)/i.test(j.hide)&&(j.hide+=" mouseleave"+k),i.show.bind("mousemove"+k,function(a){v={pageX:a.pageX,pageY:a.pageY,type:"mousemove"},l.cache.onTarget=e}),i.show.bind(j.show,n),(h.show.ready||h.prerender)&&n(b)})},u=t.plugins={Corner:function(a){a=(""+a).replace(/([A-Z])/," $1").replace(/middle/gi,p).toLowerCase(),this.x=(a.match(/left|right/i)||a.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(a.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase();var b=a.charAt(0);this.precedance=b==="t"||b==="b"?i:h,this.string=function(){return this.precedance===i?this.y+this.x:this.x+this.y},this.abbrev=function(){var a=this.x.substr(0,1),b=this.y.substr(0,1);return a===b?a:this.precedance===i?b+a:a+b},this.invertx=function(a){this.x=this.x===m?o:this.x===o?m:a||this.x},this.inverty=function(a){this.y=this.y===l?n:this.y===n?l:a||this.y},this.clone=function(){return{x:this.x,y:this.y,precedance:this.precedance,string:this.string,abbrev:this.abbrev,clone:this.clone,invertx:this.invertx,inverty:this.inverty}}},offset:function(a,b){function j(a,b){c.left+=b*a.scrollLeft(),c.top+=b*a.scrollTop()}var c=a.offset(),e=a.closest("body")[0],f=b,g,h,i;if(f){do f.css("position")!=="static"&&(h=f.position(),c.left-=h.left+(parseInt(f.css("borderLeftWidth"),10)||0)+(parseInt(f.css("marginLeft"),10)||0),c.top-=h.top+(parseInt(f.css("borderTopWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0),!g&&(i=f.css("overflow"))!=="hidden"&&i!=="visible"&&(g=f));while((f=d(f[0].offsetParent)).length);g&&g[0]!==e&&j(g,1)}return c},iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))||f,fn:{attr:function(a,b){if(this.length){var c=this[0],e="title",f=d.data(c,"qtip");if(a===e&&f&&"object"==typeof f&&f.options.suppress)return arguments.length<2?d.attr(c,F):(f&&f.options.content.attr===e&&f.cache.attr&&f.set("content.text",b),this.attr(F,b))}return d.fn["attr"+E].apply(this,arguments)},clone:function(a){var b=d([]),c="title",e=d.fn["clone"+E].apply(this,arguments);return a||e.filter("["+F+"]").attr("title",function(){return d.attr(this,F)}).removeAttr(F),e}}},d.each(u.fn,function(a,b){if(!b||d.fn[a+E])return e;var c=d.fn[a+E]=d.fn[a];d.fn[a]=function(){return b.apply(this,arguments)||c.apply(this,arguments)}}),d.ui||(d["cleanData"+E]=d.cleanData,d.cleanData=function(a){for(var b=0,e;(e=a[b])!==c;b++)try{d(e).triggerHandler("removeqtip")}catch(f){}d["cleanData"+E](a)}),t.version="@VERSION",t.nextid=0,t.inactiveEvents="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),t.zindex=15e3,t.defaults={prerender:f,id:f,overwrite:e,suppress:e,content:{text:e,attr:"title",title:{text:f,button:f}},position:{my:"top left",at:"bottom right",target:f,container:f,viewport:f,adjust:{x:0,y:0,mouse:e,resize:e,method:"flip flip"},effect:function(a,b,c){d(this).animate(b,{duration:200,queue:f})}},show:{target:f,event:"mouseenter",effect:e,delay:90,solo:f,ready:f,autofocus:f},hide:{target:f,event:"mouseleave",effect:e,delay:0,fixed:f,inactive:f,leave:"window",distance:f},style:{classes:"",widget:f,width:f,height:f,def:e},events:{render:g,move:g,show:g,hide:g,toggle:g,visible:g,hidden:g,focus:g,blur:g}},u.svg=function(a,c,e,f){var g=d(b),h=c[0],i={width:0,height:0,position:{top:1e10,left:1e10}},j,k,l,m,n;while(!h.getBBox)h=h.parentNode;if(h.getBBox&&h.parentNode){j=h.getBBox(),k=h.getScreenCTM(),l=h.farthestViewportElement||h;if(!l.createSVGPoint)return i;m=l.createSVGPoint(),m.x=j.x,m.y=j.y,n=m.matrixTransform(k),i.position.left=n.x,i.position.top=n.y,m.x+=j.width,m.y+=j.height,n=m.matrixTransform(k),i.width=n.x-i.position.left,i.height=n.y-i.position.top,i.position.left+=g.scrollLeft(),i.position.top+=g.scrollTop()}return i},u.ajax=function(a){var b=a.plugins.ajax;return"object"==typeof b?b:a.plugins.ajax=new L(a)},u.ajax.initialize="render",u.ajax.sanitize=function(a){var b=a.content,c;b&&"ajax"in b&&(c=b.ajax,typeof c!="object"&&(c=a.content.ajax={url:c}),"boolean"!=typeof c.once&&c.once&&(c.once=!!c.once))},d.extend(e,t.defaults,{content:{ajax:{loading:e,once:e}}}),u.tip=function(a){var b=a.plugins.tip;return"object"==typeof b?b:a.plugins.tip=new N(a)},u.tip.initialize="render",u.tip.sanitize=function(a){var b=a.style,c;b&&"tip"in b&&(c=a.style.tip,typeof c!="object"&&(a.style.tip={corner:c}),/string|boolean/i.test(typeof c.corner)||(c.corner=e),typeof c.width!="number"&&delete c.width,typeof c.height!="number"&&delete c.height,typeof c.border!="number"&&c.border!==e&&delete c.border,typeof c.offset!="number"&&delete c.offset)},d.extend(e,t.defaults,{style:{tip:{corner:e,mimic:f,width:6,height:6,border:e,offset:0}}}),u.modal=function(a){var b=a.plugins.modal;return"object"==typeof b?b:a.plugins.modal=new O(a)},u.modal.initialize="render",u.modal.sanitize=function(a){a.show&&(typeof a.show.modal!="object"?a.show.modal={on:!!a.show.modal}:typeof a.show.modal.on=="undefined"&&(a.show.modal.on=e))},u.modal.zindex=t.zindex-200,u.modal.focusable=["a[href]","area[href]","input","select","textarea","button","iframe","object","embed","[tabindex]","[contenteditable]"],d.extend(e,t.defaults,{show:{modal:{on:f,effect:e,blur:e,stealfocus:e,escape:e}}}),u.viewport=function(c,d,e,f,g,q,t){function L(a,b,c,e,f,g,h,i,j){var k=d[f],l=w[a],m=y[a],n=c===s,o=-E.offset[f]+D.offset[f]+D["scroll"+f],q=l===f?j:l===g?-j:-j/2,t=m===f?i:m===g?-i:-i/2,u=G&&G.size?G.size[h]||0:0,v=G&&G.corner&&G.corner.precedance===a&&!n?u:0,x=o-k+v,z=k+j-D[h]-o+v,A=q-(w.precedance===a||l===w[b]?t:0)-(m===p?i/2:0);return n?(v=G&&G.corner&&G.corner.precedance===b?u:0,A=(l===f?1:-1)*q-v,d[f]+=x>0?x:z>0?-z:0,d[f]=Math.max(-E.offset[f]+D.offset[f]+(v&&G.corner[a]===p?G.offset:0),k-A,Math.min(Math.max(-E.offset[f]+D.offset[f]+D[h],k+A),d[f]))):(e*=c===r?2:0,x>0&&(l!==f||z>0)?(d[f]-=A+e,J["invert"+a](f)):z>0&&(l!==g||x>0)&&(d[f]-=(l===p?-A:A)+e,J["invert"+a](g)),d[f]<o&&-d[f]>z&&(d[f]=k,J=w.clone())),d[f]-k}var u=e.target,v=c.elements.tooltip,w=e.my,y=e.at,z=e.adjust,A=z.method.split(" "),B=A[0],C=A[1]||A[0],D=e.viewport,E=e.container,F=c.cache,G=c.plugins.tip,H={left:0,top:0},I,J,K;if(!D.jquery||u[0]===a||u[0]===b.body||z.method==="none")return H;I=v.css("position")==="fixed",D={elem:D,height:D[(D[0]===a?"h":"outerH")+"eight"](),width:D[(D[0]===a?"w":"outerW")+"idth"](),scrollleft:I?0:D.scrollLeft(),scrolltop:I?0:D.scrollTop(),offset:D.offset()||{left:0,top:0}},E={elem:E,scrollLeft:E.scrollLeft(),scrollTop:E.scrollTop(),offset:E.offset()||{left:0,top:0}};if(B!=="shift"||C!=="shift")J=w.clone();return H={left:B!=="none"?L(h,i,B,z.x,m,o,j,f,q):0,top:C!=="none"?L(i,h,C,z.y,l,n,k,g,t):0},J&&F.lastClass!==(K=x+"-pos-"+J.abbrev())&&v.removeClass(c.cache.lastClass).addClass(c.cache.lastClass=K),H},u.imagemap=function(a,b,c,e){function v(a,b,c){var d=0,e=1,f=1,g=0,h=0,i=a.width,j=a.height;while(i>0&&j>0&&e>0&&f>0){i=Math.floor(i/2),j=Math.floor(j/2),c.x===m?e=i:c.x===o?e=a.width-i:e+=Math.floor(i/2),c.y===l?f=j:c.y===n?f=a.height-j:f+=Math.floor(j/2),d=b.length;while(d--){if(b.length<2)break;g=b[d][0]-a.position.left,h=b[d][1]-a.position.top,(c.x===m&&g>=e||c.x===o&&g<=e||c.x===p&&(g<e||g>a.width-e)||c.y===l&&h>=f||c.y===n&&h<=f||c.y===p&&(h<f||h>a.height-f))&&b.splice(d,1)}}return{left:b[0][0],top:b[0][1]}}b.jquery||(b=d(b));var f=a.cache.areas={},g=(b[0].shape||b.attr("shape")).toLowerCase(),h=b[0].coords||b.attr("coords"),i=h.split(","),j=[],k=d('img[usemap="#'+b.parent("map").attr("name")+'"]'),q=k.offset(),r={width:0,height:0,position:{top:1e10,right:0,bottom:0,left:1e10}},s=0,t=0,u;q.left+=Math.ceil((k.outerWidth()-k.width())/2),q.top+=Math.ceil((k.outerHeight()-k.height())/2);if(g==="poly"){s=i.length;while(s--)t=[parseInt(i[--s],10),parseInt(i[s+1],10)],t[0]>r.position.right&&(r.position.right=t[0]),t[0]<r.position.left&&(r.position.left=t[0]),t[1]>r.position.bottom&&(r.position.bottom=t[1]),t[1]<r.position.top&&(r.position.top=t[1]),j.push(t)}else{s=-1;while(s++<i.length)j.push(parseInt(i[s],10))}switch(g){case"rect":r={width:Math.abs(j[2]-j[0]),height:Math.abs(j[3]-j[1]),position:{left:Math.min(j[0],j[2]),top:Math.min(j[1],j[3])}};break;case"circle":r={width:j[2]+2,height:j[2]+2,position:{left:j[0],top:j[1]}};break;case"poly":r.width=Math.abs(r.position.right-r.position.left),r.height=Math.abs(r.position.bottom-r.position.top),c.abbrev()==="c"?r.position={left:r.position.left+r.width/2,top:r.position.top+r.height/2}:(f[c+h]||(r.position=v(r,j.slice(),c),e&&(e[0]==="flip"||e[1]==="flip")&&(r.offset=v(r,j.slice(),{x:c.x===m?o:c.x===o?m:p,y:c.y===l?n:c.y===n?l:p}),r.offset.left-=r.position.left,r.offset.top-=r.position.top),f[c+h]=r),r=f[c+h]),r.width=r.height=0}return r.position.left+=q.left,r.position.top+=q.top,r},u.bgiframe=function(a){var b=d.browser,c=a.plugins.bgiframe;return d("select, object").length<1||!b.msie||(""+b.version).charAt(0)!=="6"?f:"object"==typeof c?c:a.plugins.bgiframe=new P(a)},u.bgiframe.initialize="render"})})(window,document);
|
js/menu-editor.js
CHANGED
@@ -1,51 +1,277 @@
|
|
1 |
//(c) W-Shadow
|
2 |
|
|
|
3 |
/** @namespace wsEditorData */
|
4 |
|
5 |
var wsIdCounter = 0;
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
(function ($){
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
/*
|
10 |
* Utility function for generating pseudo-random alphanumeric menu IDs.
|
11 |
* Rationale: Simpler than atomically auto-incrementing or globally unique IDs.
|
12 |
*/
|
13 |
-
function randomMenuId(size){
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
var text = "";
|
19 |
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
20 |
|
21 |
-
for( var i=0; i < size; i++ )
|
22 |
-
|
|
|
23 |
|
24 |
-
return
|
25 |
}
|
26 |
-
|
27 |
function outputWpMenu(menu){
|
|
|
|
|
|
|
28 |
//Remove the current menu data
|
29 |
-
|
30 |
$('#ws_submenu_box').empty();
|
31 |
-
|
32 |
-
$('.ac_results').remove();
|
33 |
-
|
34 |
//Display the new menu
|
35 |
var i = 0;
|
36 |
-
for (var filename in
|
37 |
-
|
|
|
|
|
|
|
38 |
i++;
|
39 |
}
|
40 |
-
|
41 |
//Automatically select the first top-level menu
|
42 |
-
|
43 |
}
|
44 |
|
45 |
/*
|
46 |
* Create edit widgets for a top-level menu and its submenus and append them all to the DOM.
|
47 |
*
|
48 |
-
* Inputs :
|
49 |
* menu - an object containing menu data
|
50 |
* afterNode - if specified, the new menu widget will be inserted after this node. Otherwise,
|
51 |
* it will be added to the end of the list.
|
@@ -55,205 +281,315 @@ function outputWpMenu(menu){
|
|
55 |
function outputTopMenu(menu, afterNode){
|
56 |
//Create a container for menu items, even if there are none
|
57 |
var submenu = buildSubmenu(menu.items);
|
58 |
-
|
59 |
//Create the menu widget
|
60 |
-
var menu_obj =
|
61 |
menu_obj.data('submenu_id', submenu.attr('id'));
|
62 |
-
|
|
|
63 |
//Display
|
64 |
submenu.appendTo('#ws_submenu_box');
|
65 |
-
|
|
|
66 |
$(afterNode).after(menu_obj);
|
67 |
} else {
|
68 |
menu_obj.appendTo('#ws_menu_box');
|
69 |
}
|
70 |
-
|
71 |
return {
|
72 |
'menu' : menu_obj,
|
73 |
'submenu' : submenu
|
74 |
};
|
75 |
}
|
76 |
|
77 |
-
/*
|
78 |
-
* Create an edit widget for a top-level menu.
|
79 |
-
*/
|
80 |
-
function buildTopMenu(menu){
|
81 |
-
var subclass = '';
|
82 |
-
if ( menu.separator ) {
|
83 |
-
subclass = subclass + ' ws_menu_separator';
|
84 |
-
}
|
85 |
-
|
86 |
-
//Create the menu HTML
|
87 |
-
var menu_obj = $('<div></div>')
|
88 |
-
.attr('class', "ws_container ws_menu "+subclass)
|
89 |
-
.attr('id', 'ws-topmenu-'+(wsIdCounter++))
|
90 |
-
.data('defaults', menu.defaults)
|
91 |
-
.data('initial_values', getCurrentFieldValues(menu))
|
92 |
-
.data('field_editors_created', false);
|
93 |
-
|
94 |
-
//Add a header and a container for property editors (to improve performance
|
95 |
-
//the editors themselves are created later, when the user tries to access them
|
96 |
-
//for the first time).
|
97 |
-
var contents = [];
|
98 |
-
contents.push(
|
99 |
-
'<div class="ws_item_head">',
|
100 |
-
menu.separator ? '' : '<a class="ws_edit_link"> </a><div class="ws_flag_container"> </div>',
|
101 |
-
'<span class="ws_item_title">',
|
102 |
-
((menu.menu_title!=null) ? menu.menu_title : menu.defaults.menu_title),
|
103 |
-
' </span>',
|
104 |
-
'</div>',
|
105 |
-
'<div class="ws_editbox" style="display: none;"></div>'
|
106 |
-
);
|
107 |
-
menu_obj.append(contents.join(''));
|
108 |
-
|
109 |
-
//Apply flags based on the item's state
|
110 |
-
if (menu.missing && !getFieldValue(menu, 'custom', false)) {
|
111 |
-
addMenuFlag(menu_obj, 'missing');
|
112 |
-
}
|
113 |
-
if (menu.hidden) {
|
114 |
-
addMenuFlag(menu_obj, 'hidden');
|
115 |
-
}
|
116 |
-
if (menu.unused) {
|
117 |
-
addMenuFlag(menu_obj, 'unused');
|
118 |
-
}
|
119 |
-
if (getFieldValue(menu, 'custom', false)) {
|
120 |
-
addMenuFlag(menu_obj, 'custom_item');
|
121 |
-
}
|
122 |
-
|
123 |
-
if ( !menu.separator ){
|
124 |
-
//Allow the user to drag menu items to top-level menus
|
125 |
-
menu_obj.droppable({
|
126 |
-
'hoverClass' : 'ws_menu_drop_hover',
|
127 |
-
|
128 |
-
'accept' : (function(thing){
|
129 |
-
return thing.hasClass('ws_item');
|
130 |
-
}),
|
131 |
-
|
132 |
-
'drop' : (function(event, ui){
|
133 |
-
var droppedItemData = readItemState(ui.draggable);
|
134 |
-
var new_item = buildMenuItem(droppedItemData);
|
135 |
-
var submenu = $('#' + menu_obj.data('submenu_id'));
|
136 |
-
submenu.append(new_item);
|
137 |
-
ui.draggable.remove();
|
138 |
-
})
|
139 |
-
});
|
140 |
-
}
|
141 |
-
|
142 |
-
return menu_obj;
|
143 |
-
}
|
144 |
-
|
145 |
/*
|
146 |
* Create and populate a submenu container.
|
147 |
*/
|
148 |
function buildSubmenu(items){
|
149 |
//Create a container for menu items, even if there are none
|
150 |
-
var submenu = $('<div class="ws_submenu"style="display:none;"></div>');
|
151 |
submenu.attr('id', 'ws-submenu-'+(wsIdCounter++));
|
152 |
-
|
153 |
-
//Only show menus that have items.
|
154 |
//Skip arrays (with a length) because filled menus are encoded as custom objects.
|
155 |
var entry = null;
|
156 |
-
if (items
|
157 |
-
|
158 |
-
entry = buildMenuItem(
|
159 |
if ( entry ){
|
|
|
160 |
submenu.append(entry);
|
161 |
}
|
162 |
-
}
|
163 |
}
|
164 |
-
|
165 |
//Make the submenu sortable
|
166 |
makeBoxSortable(submenu);
|
167 |
-
|
168 |
return submenu;
|
169 |
}
|
170 |
|
171 |
-
|
172 |
-
* Create an edit widget for a menu
|
|
|
|
|
|
|
|
|
173 |
*/
|
174 |
-
function buildMenuItem(
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
.
|
181 |
-
.data('
|
182 |
.data('field_editors_created', false);
|
183 |
-
|
|
|
|
|
|
|
|
|
|
|
184 |
//Add a header and a container for property editors (to improve performance
|
185 |
//the editors themselves are created later, when the user tries to access them
|
186 |
//for the first time).
|
187 |
var contents = [];
|
188 |
contents.push(
|
189 |
'<div class="ws_item_head">',
|
190 |
-
'<a class="ws_edit_link"> </a><div class="ws_flag_container"> </div>',
|
|
|
191 |
'<span class="ws_item_title">',
|
192 |
-
((
|
193 |
' </span>',
|
|
|
194 |
'</div>',
|
195 |
'<div class="ws_editbox" style="display: none;"></div>'
|
196 |
);
|
197 |
item.append(contents.join(''));
|
198 |
-
|
199 |
//Apply flags based on the item's state
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
if (entry.hidden) {
|
204 |
-
addMenuFlag(item, 'hidden');
|
205 |
-
}
|
206 |
-
if (entry.unused) {
|
207 |
-
addMenuFlag(item, 'unused');
|
208 |
}
|
209 |
-
|
210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
211 |
}
|
212 |
-
|
213 |
return item;
|
214 |
}
|
215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
216 |
/*
|
217 |
* List of all menu fields that have an associated editor
|
218 |
-
*/
|
219 |
var knownMenuFields = {
|
220 |
-
'menu_title' : {
|
221 |
caption : 'Menu title',
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
caption: 'Required capability',
|
230 |
-
standardCaption : true,
|
231 |
-
advanced : false,
|
232 |
-
type : 'text',
|
233 |
defaultValue: 'read',
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
247 |
caption: "Window title",
|
248 |
standardCaption : true,
|
249 |
-
advanced : true
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
},
|
254 |
-
'open_in' : {
|
255 |
caption: 'Open in',
|
256 |
-
standardCaption : true,
|
257 |
advanced : true,
|
258 |
type : 'select',
|
259 |
options : {
|
@@ -263,77 +599,94 @@ var knownMenuFields = {
|
|
263 |
},
|
264 |
defaultValue: 'same_window',
|
265 |
visible: false
|
266 |
-
},
|
267 |
-
|
|
|
268 |
caption: 'CSS classes',
|
269 |
-
standardCaption : true,
|
270 |
advanced : true,
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
},
|
275 |
-
'icon_url' : {
|
276 |
caption: 'Icon URL',
|
277 |
-
standardCaption : true,
|
278 |
-
advanced : true,
|
279 |
type : 'icon_selector',
|
|
|
280 |
defaultValue: 'div',
|
281 |
-
|
282 |
-
|
283 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
284 |
caption: 'Hook name',
|
285 |
-
standardCaption : true,
|
286 |
advanced : true,
|
287 |
-
|
288 |
-
|
289 |
-
visible: true
|
290 |
-
},
|
291 |
-
'custom' : {
|
292 |
-
caption : 'Custom',
|
293 |
-
standardCaption : false,
|
294 |
-
advanced: true,
|
295 |
-
type: 'checkbox',
|
296 |
-
defaultValue: false,
|
297 |
-
visible: false
|
298 |
-
}
|
299 |
};
|
300 |
|
301 |
/*
|
302 |
* Create editors for the visible fields of a menu entry and append them to the specified node.
|
303 |
*/
|
304 |
-
function buildEditboxFields(
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
var advancedFields = $('<div class="ws_edit_panel ws_advanced"></div>').appendTo(
|
309 |
-
|
310 |
-
if ( hideAdvancedSettings ){
|
311 |
advancedFields.css('display', 'none');
|
312 |
}
|
313 |
-
|
314 |
-
for (var field_name in
|
315 |
-
if (!
|
|
|
|
|
|
|
|
|
|
|
316 |
continue;
|
317 |
}
|
318 |
-
|
|
|
319 |
if (field){
|
320 |
-
if (
|
321 |
advancedFields.append(field);
|
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 |
-
|
332 |
//Add a link that shows/hides advanced fields
|
333 |
-
|
334 |
'<div class="ws_toggle_container"><a href="#" class="ws_toggle_advanced_fields"'+
|
335 |
-
(hideAdvancedSettings?'':' style="display:none;"')+'>'+
|
336 |
-
(hideAdvancedSettings?captionShowAdvanced:captionHideAdvanced)
|
337 |
+'</a></div>'
|
338 |
);
|
339 |
}
|
@@ -345,65 +698,60 @@ function buildEditboxField(entry, field_name, field_settings){
|
|
345 |
if (typeof entry[field_name] === 'undefined') {
|
346 |
return null; //skip fields this entry doesn't have
|
347 |
}
|
348 |
-
|
349 |
-
|
350 |
-
var value = (entry[field_name]!=null)?entry[field_name]:default_value;
|
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">');
|
358 |
var option = null;
|
359 |
for( var optionTitle in field_settings.options ){
|
|
|
|
|
|
|
360 |
option = $('<option>')
|
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 |
}
|
368 |
break;
|
369 |
-
|
370 |
case 'checkbox':
|
371 |
-
inputBox = $('<label><input type="checkbox"
|
372 |
field_settings.caption+'</label>'
|
373 |
);
|
374 |
break;
|
375 |
|
|
|
|
|
|
|
|
|
|
|
376 |
case 'icon_selector':
|
377 |
-
inputBox = $(basicTextField)
|
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)
|
384 |
}
|
385 |
-
|
386 |
-
|
387 |
var className = "ws_edit_field ws_edit_field-"+field_name;
|
388 |
-
if(
|
389 |
-
className += ' ws_input_default';
|
390 |
-
}
|
391 |
-
|
392 |
-
var hasDropdown = (typeof(field_settings['addDropdown']) != 'undefined') && field_settings.addDropdown;
|
393 |
-
if ( hasDropdown ){
|
394 |
className += ' ws_has_dropdown';
|
395 |
}
|
396 |
-
|
397 |
-
var editField = $('<div>' + (field_settings.standardCaption?(field_settings.caption+'<br>'):'') + '</div>')
|
398 |
.attr('class', className)
|
399 |
.append(inputBox);
|
400 |
-
|
401 |
-
if (
|
402 |
//Add a dropdown button
|
403 |
-
var dropdownId =
|
404 |
-
if ( typeof(field_settings.addDropdown) == 'string' ){
|
405 |
-
dropdownId = field_settings.addDropdown;
|
406 |
-
}
|
407 |
editField.append(
|
408 |
$('<input type="button" value="▼">')
|
409 |
.addClass('button ws_dropdown_button')
|
@@ -411,70 +759,157 @@ function buildEditboxField(entry, field_name, field_settings){
|
|
411 |
.data('dropdownId', dropdownId)
|
412 |
);
|
413 |
}
|
414 |
-
|
415 |
editField
|
416 |
-
.append('<img src="'+imagesUrl+'/transparent16.png" class="ws_reset_button" title="Reset to default value"> </img>')
|
417 |
-
.data('field_name', field_name)
|
418 |
-
|
419 |
-
|
420 |
if ( !field_settings.visible ){
|
421 |
editField.css('display', 'none');
|
422 |
}
|
423 |
-
|
424 |
-
return editField;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
425 |
}
|
426 |
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
var
|
435 |
-
|
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 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
|
|
|
|
450 |
}
|
451 |
}
|
452 |
|
453 |
-
|
454 |
-
*
|
455 |
-
*
|
|
|
456 |
*/
|
457 |
-
function
|
458 |
-
var
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
values[field_name] = entry[field_name];
|
465 |
}
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
470 |
}
|
471 |
|
472 |
/*
|
473 |
-
* Get the current value of a single menu field.
|
474 |
*
|
475 |
-
* If the specified field is not set, this function will attempt to retrieve it
|
476 |
* from the "defaults" property of the menu object. If *that* fails, it will return
|
477 |
-
* the value of the optional third argument defaultValue.
|
478 |
*/
|
479 |
function getFieldValue(entry, fieldName, defaultValue){
|
480 |
if ( (typeof entry[fieldName] === 'undefined') || (entry[fieldName] === null) ) {
|
@@ -505,91 +940,74 @@ function makeBoxSortable(menuBox){
|
|
505 |
Parsing & encoding menu inputs
|
506 |
***************************************************************************/
|
507 |
|
508 |
-
|
509 |
* Encode the current menu structure as JSON
|
510 |
*
|
511 |
-
*
|
512 |
-
* A JSON-encoded string representing the current menu tree loaded in the editor.
|
513 |
*/
|
514 |
-
function encodeMenuAsJSON(){
|
515 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
516 |
return $.toJSON(tree);
|
517 |
}
|
518 |
|
519 |
function readMenuTreeState(){
|
520 |
var tree = {};
|
521 |
var menu_position = 0;
|
522 |
-
|
523 |
//Gather all menus and their items
|
524 |
-
$('#ws_menu_box
|
525 |
-
var menu =
|
526 |
-
|
527 |
//Attach the current menu to the main struct
|
528 |
var filename = (menu.file !== null)?menu.file:menu.defaults.file;
|
529 |
tree[filename] = menu;
|
530 |
});
|
531 |
-
|
532 |
-
return tree;
|
533 |
-
}
|
534 |
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
* Inputs :
|
539 |
-
* menu_div - DOM node, typically one with the .ws_menu class.
|
540 |
-
* position - The current menu position (int).
|
541 |
-
*
|
542 |
-
* Output :
|
543 |
-
* A menu object in the tree format.
|
544 |
-
*
|
545 |
-
*/
|
546 |
-
function readMenuState(menu_div, position){
|
547 |
-
menu_div = $(menu_div);
|
548 |
-
var menu = readAllFields(menu_div);
|
549 |
-
|
550 |
-
menu.defaults = menu_div.data('defaults');
|
551 |
-
|
552 |
-
menu.position = position;
|
553 |
-
menu.defaults.position = position; //the real default value will later overwrite this
|
554 |
-
|
555 |
-
menu.separator = menu_div.hasClass('ws_menu_separator');
|
556 |
-
menu.hidden = menu_div.hasClass('ws_hidden');
|
557 |
-
|
558 |
-
//Gather the menu's items, if any
|
559 |
-
menu.items = {};
|
560 |
-
var item_position = 0;
|
561 |
-
$('#'+menu_div.data('submenu_id')).find('.ws_item').each(function (i) {
|
562 |
-
var item = readItemState(this, item_position++);
|
563 |
-
menu.items[ (item.file?item.file:item.defaults.file) ] = item;
|
564 |
-
});
|
565 |
-
|
566 |
-
return menu;
|
567 |
}
|
568 |
|
569 |
-
|
570 |
* Extract the current menu item settings from its editor widget.
|
571 |
*
|
572 |
-
*
|
573 |
-
*
|
574 |
-
*
|
575 |
-
*
|
576 |
*/
|
577 |
-
function readItemState(
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
item
|
582 |
-
|
|
|
|
|
583 |
//Save the position data
|
584 |
-
if ( typeof position == 'undefined' ){
|
585 |
-
position = 0;
|
586 |
-
}
|
587 |
item.position = position;
|
588 |
-
item.defaults.position = position;
|
589 |
-
|
590 |
-
|
591 |
-
item.hidden =
|
592 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
593 |
return item;
|
594 |
}
|
595 |
|
@@ -597,131 +1015,216 @@ function readItemState(item_div, position){
|
|
597 |
* Extract the values of all menu/item fields present in a container node
|
598 |
*
|
599 |
* Inputs:
|
600 |
-
* container - a jQuery collection representing the node to read.
|
601 |
*/
|
602 |
function readAllFields(container){
|
603 |
if ( !container.hasClass('ws_container') ){
|
604 |
-
container = container.
|
605 |
}
|
606 |
-
|
607 |
if ( !container.data('field_editors_created') ){
|
608 |
-
return container.data('
|
609 |
}
|
610 |
-
|
611 |
var state = {};
|
612 |
-
|
613 |
//Iterate over all fields of the item
|
614 |
container.find('.ws_edit_field').each(function() {
|
615 |
var field = $(this);
|
616 |
-
|
617 |
//Get the name of this field
|
618 |
-
field_name = field.data('field_name');
|
619 |
//Skip if unnamed
|
620 |
-
if (!field_name)
|
621 |
-
|
|
|
|
|
622 |
//Find the field (usually an input or select element).
|
623 |
-
input_box = field.find('.ws_field_value');
|
624 |
-
|
625 |
//Save null if default used, custom value otherwise
|
626 |
if (field.hasClass('ws_input_default')){
|
627 |
state[field_name] = null;
|
628 |
} else {
|
629 |
-
|
630 |
-
state[field_name] = input_box.is(':checked');
|
631 |
-
} else {
|
632 |
-
state[field_name] = input_box.val();
|
633 |
-
}
|
634 |
}
|
|
|
635 |
});
|
636 |
-
|
|
|
|
|
|
|
|
|
637 |
return state;
|
638 |
}
|
639 |
|
640 |
|
641 |
/***************************************************************************
|
642 |
-
|
643 |
***************************************************************************/
|
644 |
|
645 |
var item_flags = {
|
646 |
-
'
|
647 |
-
'unused'
|
648 |
-
'
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
function addMenuFlag(item, flag){
|
653 |
item = $(item);
|
654 |
-
|
655 |
var item_class = 'ws_' + flag;
|
656 |
var img_class = 'ws_' + flag + '_flag';
|
657 |
-
|
658 |
-
item.
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
flag_container.
|
|
|
|
|
|
|
|
|
|
|
663 |
}
|
664 |
}
|
665 |
|
666 |
-
function
|
667 |
-
|
668 |
-
var item_class = 'ws_' + flag;
|
669 |
-
var img_class = 'ws_' + flag + '_flag';
|
670 |
-
|
671 |
-
item.removeClass('ws_' + flag);
|
672 |
-
item.find('.' + img_class).remove();
|
673 |
}
|
674 |
|
675 |
-
|
676 |
-
|
677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
678 |
} else {
|
679 |
-
|
680 |
}
|
|
|
681 |
}
|
682 |
|
683 |
-
function
|
684 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
685 |
}
|
686 |
|
687 |
-
function
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
693 |
}
|
694 |
|
|
|
|
|
|
|
|
|
695 |
//Cut & paste stuff
|
696 |
var menu_in_clipboard = null;
|
697 |
-
var submenu_in_clipboard = null;
|
698 |
-
var item_in_clipboard = null;
|
699 |
var ws_paste_count = 0;
|
700 |
|
701 |
$(document).ready(function(){
|
702 |
-
|
|
|
703 |
knownMenuFields['open_in'].visible = true;
|
|
|
|
|
|
|
704 |
}
|
705 |
-
|
706 |
//Make the top menu box sortable (we only need to do this once)
|
707 |
var mainMenuBox = $('#ws_menu_box');
|
708 |
makeBoxSortable(mainMenuBox);
|
709 |
-
|
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;
|
717 |
-
|
718 |
var container = $(this);
|
719 |
if ( container.hasClass('ws_active') ){
|
720 |
return;
|
721 |
}
|
722 |
-
|
723 |
//Highlight the active item and un-highlight the previous one
|
724 |
-
container.addClass('ws_active')
|
725 |
container.siblings('.ws_active').removeClass('ws_active');
|
726 |
if ( container.hasClass('ws_menu') ){
|
727 |
//Show/hide the appropriate submenu
|
@@ -731,19 +1234,20 @@ $(document).ready(function(){
|
|
731 |
currentVisibleSubmenu = $('#'+container.data('submenu_id')).show();
|
732 |
}
|
733 |
}));
|
734 |
-
|
735 |
//Show/hide a menu's properties
|
736 |
-
|
737 |
-
var container = $(this).parents('.ws_container').first();
|
738 |
var box = container.find('.ws_editbox');
|
739 |
-
|
740 |
-
//For performance, the property editors for each menu are only created
|
741 |
//when the user tries to access access them for the first time.
|
742 |
if ( !container.data('field_editors_created') ){
|
743 |
-
buildEditboxFields(box, container.data('
|
744 |
container.data('field_editors_created', true);
|
|
|
745 |
}
|
746 |
-
|
747 |
$(this).toggleClass('ws_edit_link_expanded');
|
748 |
//show/hide the editbox
|
749 |
if ($(this).hasClass('ws_edit_link_expanded')){
|
@@ -754,235 +1258,385 @@ $(document).ready(function(){
|
|
754 |
box.hide();
|
755 |
}
|
756 |
}));
|
757 |
-
|
758 |
//The "Default" button : Reset to default value when clicked
|
759 |
-
|
760 |
-
//Find the field div (it holds the
|
761 |
-
var field = $(this).
|
|
|
762 |
//Find the related input field
|
763 |
var input = field.find('.ws_field_value');
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
}
|
775 |
-
|
776 |
-
|
777 |
-
|
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);
|
784 |
var field = input.parents('.ws_edit_field').first();
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
|
789 |
-
|
|
|
790 |
}
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
-
|
801 |
-
|
802 |
-
|
803 |
-
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
|
810 |
-
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
|
817 |
-
|
818 |
-
|
|
|
|
|
|
|
|
|
|
|
819 |
}
|
820 |
menuEditorNode.on('click change', '.ws_field_value', fieldValueChange);
|
821 |
|
822 |
//Show/hide advanced fields
|
823 |
-
|
824 |
var self = $(this);
|
825 |
var advancedFields = self.parents('.ws_container').first().find('.ws_advanced');
|
826 |
-
|
827 |
if ( advancedFields.is(':visible') ){
|
828 |
advancedFields.hide();
|
829 |
-
self.text(captionShowAdvanced);
|
830 |
} else {
|
831 |
advancedFields.show();
|
832 |
-
self.text(captionHideAdvanced);
|
833 |
}
|
834 |
-
|
835 |
return false;
|
836 |
});
|
837 |
-
|
838 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
839 |
/***************************************************************************
|
840 |
-
|
841 |
***************************************************************************/
|
842 |
|
843 |
-
var
|
844 |
-
|
845 |
-
|
846 |
-
|
847 |
-
|
848 |
-
|
849 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
}
|
854 |
-
};
|
855 |
-
|
856 |
-
//Show/hide the capability dropdown list when the button is clicked
|
857 |
-
$('#ws_menu_editor input.ws_dropdown_button').live('click',function(event){
|
858 |
var button = $(this);
|
859 |
-
|
860 |
-
|
861 |
-
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
|
866 |
-
|
867 |
-
|
868 |
-
|
869 |
-
|
870 |
-
|
871 |
-
|
872 |
-
|
|
|
|
|
|
|
873 |
return;
|
874 |
}
|
875 |
-
dropdown.currentOwner = this; //Got ye now!
|
876 |
|
877 |
-
|
878 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
879 |
|
880 |
-
|
881 |
-
|
882 |
|
883 |
-
//Move the
|
884 |
var inputPos = inputBox.offset();
|
885 |
-
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
|
896 |
-
|
897 |
-
|
898 |
-
|
|
|
|
|
|
|
899 |
if ( event.which == 40 ){
|
900 |
-
$(
|
901 |
}
|
902 |
});
|
903 |
-
|
904 |
-
//Event handlers for the
|
905 |
var dropdownNodes = $('.ws_dropdown');
|
906 |
-
|
907 |
-
//Hide capability
|
908 |
dropdownNodes.blur(function(event){
|
909 |
-
|
910 |
-
|
911 |
-
dropdown.list.hide();
|
912 |
-
/*
|
913 |
-
* Hackiness : make sure the list doesn't disappear & immediately reappear
|
914 |
-
* when the event that caused it to lose focus was the user clicking on the
|
915 |
-
* dropdown button.
|
916 |
-
*/
|
917 |
-
dropdown.timeoutForgetOwner = setTimeout(
|
918 |
-
(function(){
|
919 |
-
dropdown.currentOwner = null;
|
920 |
-
}),
|
921 |
-
200
|
922 |
-
);
|
923 |
});
|
924 |
-
|
925 |
dropdownNodes.keydown(function(event){
|
926 |
-
|
927 |
-
|
928 |
-
//Also hide it when the user presses Esc
|
929 |
if ( event.which == 27 ){
|
930 |
-
|
931 |
-
|
932 |
-
|
933 |
-
if ( dropdown.currentOwner ){
|
934 |
-
$(dropdown.currentOwner).parent().find('input.ws_field_value').focus();
|
935 |
}
|
936 |
-
|
937 |
-
|
938 |
//Select an item & hide the list when the user presses Enter or Tab
|
939 |
} else if ( (event.which == 13) || (event.which == 9) ){
|
940 |
-
|
941 |
-
|
942 |
-
|
943 |
-
|
944 |
-
|
945 |
-
|
|
|
946 |
}
|
947 |
-
|
948 |
-
inputBox.focus();
|
949 |
-
dropdown.currentOwner = null;
|
950 |
-
|
951 |
event.preventDefault();
|
952 |
}
|
953 |
});
|
954 |
-
|
955 |
-
//Eat Tab keys to prevent focus theft. Required to make the "select item on Tab" thing work.
|
956 |
dropdownNodes.keyup(function(event){
|
957 |
if ( event.which == 9 ){
|
958 |
event.preventDefault();
|
959 |
}
|
960 |
-
})
|
961 |
-
|
962 |
-
|
963 |
//Update the input & hide the list when an option is clicked
|
964 |
dropdownNodes.click(function(){
|
965 |
-
|
966 |
-
|
967 |
-
|
968 |
-
|
|
|
969 |
}
|
970 |
-
dropdown.list.hide();
|
971 |
-
|
972 |
-
var inputBox = $(dropdown.currentOwner).parent().find('input.ws_field_value');
|
973 |
-
inputBox.val(dropdown.list.val()).change().focus();
|
974 |
-
dropdown.currentOwner = null;
|
975 |
});
|
976 |
-
|
977 |
//Highlight an option when the user mouses over it (doesn't work in IE)
|
978 |
dropdownNodes.mousemove(function(event){
|
979 |
if ( !event.target ){
|
980 |
return;
|
981 |
}
|
982 |
-
|
983 |
-
var option =
|
984 |
-
if (
|
985 |
-
option.
|
986 |
}
|
987 |
});
|
988 |
|
@@ -1000,23 +1654,23 @@ $(document).ready(function(){
|
|
1000 |
//Assign the selected icon to the menu.
|
1001 |
if (currentIconButton) {
|
1002 |
var container = currentIconButton.closest('.ws_container');
|
1003 |
-
var
|
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 =
|
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 |
-
|
1015 |
} else if (selectedIcon.data('icon-url')) {
|
1016 |
-
|
1017 |
}
|
1018 |
-
|
1019 |
-
|
|
|
1020 |
}
|
1021 |
|
1022 |
currentIconButton = null;
|
@@ -1035,11 +1689,9 @@ $(document).ready(function(){
|
|
1035 |
|
1036 |
currentIconButton = button;
|
1037 |
|
1038 |
-
var
|
1039 |
-
var
|
1040 |
-
var
|
1041 |
-
var cssClass = cssClassField.val();
|
1042 |
-
var iconUrl = iconUrlField.val();
|
1043 |
|
1044 |
var customImageOption = iconSelector.find('.ws_custom_image_icon').hide();
|
1045 |
|
@@ -1112,20 +1764,16 @@ $(document).ready(function(){
|
|
1112 |
//Set the menu icon to the attachment URL.
|
1113 |
if (currentIconButton) {
|
1114 |
var container = currentIconButton.closest('.ws_container');
|
1115 |
-
|
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 |
-
|
1122 |
-
|
1123 |
|
1124 |
//Set the new icon URL.
|
1125 |
-
|
1126 |
|
1127 |
-
|
1128 |
-
iconUrlField.change();
|
1129 |
}
|
1130 |
|
1131 |
currentIconButton = null;
|
@@ -1156,39 +1804,40 @@ $(document).ready(function(){
|
|
1156 |
currentIconButton = null;
|
1157 |
}
|
1158 |
});
|
1159 |
-
|
1160 |
-
|
1161 |
/*************************************************************************
|
1162 |
Menu toolbar buttons
|
1163 |
*************************************************************************/
|
|
|
|
|
|
|
|
|
1164 |
//Show/Hide menu
|
1165 |
$('#ws_hide_menu').click(function () {
|
1166 |
//Get the selected menu
|
1167 |
-
var selection =
|
1168 |
if (!selection.length) return;
|
1169 |
-
|
1170 |
//Mark the menu as hidden/visible
|
1171 |
-
|
1172 |
-
|
1173 |
-
|
|
|
1174 |
//Also mark all of it's submenus as hidden/visible
|
1175 |
-
|
1176 |
-
$(
|
1177 |
-
|
1178 |
-
|
1179 |
-
}
|
1180 |
-
$('#' + selection.data('submenu_id') + ' .ws_item').each(function(){
|
1181 |
-
removeMenuFlag(this, 'hidden');
|
1182 |
-
});
|
1183 |
-
}
|
1184 |
});
|
1185 |
-
|
1186 |
//Delete menu
|
1187 |
$('#ws_delete_menu').click(function () {
|
1188 |
//Get the selected menu
|
1189 |
-
var selection =
|
1190 |
if (!selection.length) return;
|
1191 |
-
|
1192 |
if (confirm('Delete this menu?')){
|
1193 |
//Delete the submenu first
|
1194 |
$('#' + selection.data('submenu_id')).remove();
|
@@ -1196,343 +1845,421 @@ $(document).ready(function(){
|
|
1196 |
selection.remove();
|
1197 |
}
|
1198 |
});
|
1199 |
-
|
1200 |
//Copy menu
|
1201 |
$('#ws_copy_menu').click(function () {
|
1202 |
//Get the selected menu
|
1203 |
-
var selection = $('#ws_menu_box
|
1204 |
if (!selection.length) return;
|
1205 |
-
|
1206 |
//Store a copy of the current menu state in clipboard
|
1207 |
-
menu_in_clipboard =
|
1208 |
});
|
1209 |
-
|
1210 |
//Cut menu
|
1211 |
$('#ws_cut_menu').click(function () {
|
1212 |
//Get the selected menu
|
1213 |
-
var selection = $('#ws_menu_box
|
1214 |
if (!selection.length) return;
|
1215 |
-
|
1216 |
//Store a copy of the current menu state in clipboard
|
1217 |
-
menu_in_clipboard =
|
1218 |
-
|
1219 |
-
//Remove the original menu and submenu
|
1220 |
-
selection.remove();
|
1221 |
$('#'+selection.data('submenu_id')).remove();
|
|
|
1222 |
});
|
1223 |
-
|
1224 |
-
//Paste menu
|
1225 |
-
$('#ws_paste_menu').click(function () {
|
1226 |
-
//Check if anything has been copied/cut
|
1227 |
-
if (!menu_in_clipboard) return;
|
1228 |
-
|
1229 |
-
var menu = $.extend(true, {}, menu_in_clipboard);
|
1230 |
|
|
|
|
|
1231 |
//The user shouldn't need to worry about giving separators a unique filename.
|
1232 |
if (menu.separator) {
|
1233 |
-
menu.defaults.file = 'separator_'
|
1234 |
}
|
1235 |
|
1236 |
-
//
|
1237 |
-
|
1238 |
-
|
1239 |
-
|
1240 |
-
|
1241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1242 |
} else {
|
1243 |
-
//Otherwise add the pasted item at the end
|
1244 |
outputTopMenu(menu);
|
1245 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1246 |
});
|
1247 |
-
|
1248 |
//New menu
|
1249 |
$('#ws_new_menu').click(function () {
|
1250 |
ws_paste_count++;
|
1251 |
-
|
1252 |
//The new menu starts out rather bare
|
1253 |
-
var randomId =
|
1254 |
-
var menu = {
|
1255 |
-
|
1256 |
-
|
1257 |
-
|
1258 |
-
file :
|
1259 |
-
|
1260 |
-
|
1261 |
-
|
1262 |
-
|
1263 |
-
|
1264 |
-
|
1265 |
-
|
1266 |
-
|
1267 |
-
|
1268 |
-
page_title : '',
|
1269 |
-
access_level : 'read',
|
1270 |
-
file : randomId,
|
1271 |
-
css_class : 'menu-top',
|
1272 |
-
icon_url : 'images/generic.png',
|
1273 |
-
hookname : randomId,
|
1274 |
-
open_in : 'same_window',
|
1275 |
-
custom: true, //Important : flag the new menu as custom, or it won't show up after saving.
|
1276 |
-
position : 0,
|
1277 |
-
separator: false
|
1278 |
-
}
|
1279 |
-
};
|
1280 |
-
|
1281 |
//Insert the new menu
|
1282 |
-
|
1283 |
-
|
|
|
1284 |
//The menus's editbox is always open
|
1285 |
result.menu.find('.ws_edit_link').click();
|
1286 |
});
|
1287 |
-
|
1288 |
//New separator
|
1289 |
-
$('#ws_new_separator').click(function () {
|
1290 |
ws_paste_count++;
|
1291 |
-
|
1292 |
//The new menu starts out rather bare
|
1293 |
-
var randomId = 'separator_'
|
1294 |
-
var menu = {
|
1295 |
-
|
1296 |
-
|
1297 |
-
|
1298 |
-
|
1299 |
-
|
1300 |
-
|
1301 |
-
hookname : null,
|
1302 |
-
position : null,
|
1303 |
-
separator : true, //Flag as a separator
|
1304 |
-
custom : null,
|
1305 |
-
open_in : null,
|
1306 |
-
items : {}, //No items
|
1307 |
-
defaults : {
|
1308 |
-
menu_title : '',
|
1309 |
-
page_title : '',
|
1310 |
access_level : 'read',
|
1311 |
file : randomId,
|
1312 |
-
|
1313 |
-
icon_url : '',
|
1314 |
-
hookname : '',
|
1315 |
-
position : 0,
|
1316 |
-
custom: false, //Separators don't need to flagged as custom to be retained.
|
1317 |
-
open_in: 'same_window',
|
1318 |
-
separator: true
|
1319 |
}
|
1320 |
-
};
|
1321 |
-
|
1322 |
-
|
1323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1324 |
});
|
1325 |
-
|
1326 |
/*************************************************************************
|
1327 |
Item toolbar buttons
|
1328 |
*************************************************************************/
|
|
|
|
|
|
|
|
|
1329 |
//Show/Hide item
|
1330 |
$('#ws_hide_item').click(function () {
|
1331 |
//Get the selected item
|
1332 |
-
var selection =
|
1333 |
if (!selection.length) return;
|
1334 |
-
|
1335 |
//Mark the item as hidden/visible
|
1336 |
-
|
|
|
|
|
1337 |
});
|
1338 |
-
|
1339 |
-
//Delete
|
1340 |
$('#ws_delete_item').click(function () {
|
1341 |
//Get the selected menu
|
1342 |
-
var selection =
|
1343 |
if (!selection.length) return;
|
1344 |
-
|
1345 |
if (confirm('Delete this menu item?')){
|
|
|
1346 |
//Delete the item
|
1347 |
selection.remove();
|
|
|
1348 |
}
|
1349 |
});
|
1350 |
-
|
1351 |
//Copy item
|
1352 |
$('#ws_copy_item').click(function () {
|
1353 |
//Get the selected item
|
1354 |
-
var selection =
|
1355 |
if (!selection.length) return;
|
1356 |
-
|
1357 |
//Store a copy of item state in the clipboard
|
1358 |
-
|
1359 |
});
|
1360 |
-
|
1361 |
//Cut item
|
1362 |
$('#ws_cut_item').click(function () {
|
1363 |
//Get the selected item
|
1364 |
-
var selection =
|
1365 |
if (!selection.length) return;
|
1366 |
-
|
1367 |
//Store a copy of item state in the clipboard
|
1368 |
-
|
1369 |
-
|
1370 |
-
|
|
|
1371 |
selection.remove();
|
|
|
1372 |
});
|
1373 |
-
|
1374 |
//Paste item
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1375 |
$('#ws_paste_item').click(function () {
|
1376 |
//Check if anything has been copied/cut
|
1377 |
-
if (!
|
1378 |
|
1379 |
-
//
|
1380 |
-
|
1381 |
-
|
1382 |
-
|
1383 |
-
//Get the selected menu
|
1384 |
-
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
1385 |
-
if (selection.length > 0) {
|
1386 |
-
//If an item is selected add the pasted item after it
|
1387 |
-
selection.after(new_item);
|
1388 |
-
} else {
|
1389 |
-
//Otherwise add the pasted item at the end
|
1390 |
-
$('#ws_submenu_box .ws_submenu:visible').append(new_item);
|
1391 |
}
|
1392 |
-
|
1393 |
-
|
|
|
|
|
1394 |
});
|
1395 |
-
|
1396 |
//New item
|
1397 |
$('#ws_new_item').click(function () {
|
1398 |
-
if ($('.ws_submenu:visible').length < 1) {
|
1399 |
-
return; //Abort if no submenu visible
|
1400 |
}
|
1401 |
-
|
1402 |
ws_paste_count++;
|
1403 |
-
|
1404 |
-
|
1405 |
-
|
1406 |
-
|
1407 |
-
|
1408 |
-
|
1409 |
-
|
1410 |
-
defaults
|
1411 |
-
|
1412 |
-
|
1413 |
-
|
1414 |
-
|
1415 |
-
|
1416 |
-
|
1417 |
-
|
1418 |
-
}
|
1419 |
-
};
|
1420 |
-
|
1421 |
var menu = buildMenuItem(entry);
|
1422 |
-
|
1423 |
-
|
1424 |
-
|
1425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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){
|
1436 |
-
return str.replace(/^\s+|\s+$/g, "");
|
1437 |
-
}
|
1438 |
-
|
1439 |
var aTitle = jsTrim( $(a).find('.ws_item_title').text() );
|
1440 |
var bTitle = jsTrim( $(b).find('.ws_item_title').text() );
|
1441 |
-
|
1442 |
aTitle = aTitle.toLowerCase();
|
1443 |
bTitle = bTitle.toLowerCase();
|
1444 |
-
|
1445 |
return aTitle > bTitle ? 1 : -1;
|
1446 |
}
|
1447 |
-
|
1448 |
//Sort items in ascending order
|
1449 |
$('#ws_sort_ascending').click(function () {
|
1450 |
-
var submenu = $('#ws_submenu_box
|
1451 |
-
if (submenu.length < 1) {
|
1452 |
-
return; //Abort if no submenu visible
|
1453 |
}
|
1454 |
-
|
1455 |
submenu.find('.ws_container').sort(compareMenus);
|
1456 |
});
|
1457 |
-
|
1458 |
//Sort items in descending order
|
1459 |
$('#ws_sort_descending').click(function () {
|
1460 |
-
var submenu = $('#ws_submenu_box
|
1461 |
-
if (submenu.length < 1) {
|
1462 |
-
return; //Abort if no submenu visible
|
1463 |
}
|
1464 |
-
|
1465 |
submenu.find('.ws_container').sort((function(a, b){
|
1466 |
return -compareMenus(a, b);
|
1467 |
}));
|
1468 |
});
|
1469 |
-
|
1470 |
//==============================================
|
1471 |
// Main buttons
|
1472 |
//==============================================
|
1473 |
-
|
1474 |
//Save Changes - encode the current menu as JSON and save
|
1475 |
$('#ws_save_menu').click(function () {
|
1476 |
-
var
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1477 |
$('#ws_data').val(data);
|
|
|
|
|
1478 |
$('#ws_main_form').submit();
|
1479 |
});
|
1480 |
-
|
1481 |
//Load default menu - load the default WordPress menu
|
1482 |
$('#ws_load_menu').click(function () {
|
1483 |
if (confirm('Are you sure you want to load the default WordPress menu?')){
|
1484 |
-
outputWpMenu(defaultMenu);
|
1485 |
}
|
1486 |
});
|
1487 |
-
|
1488 |
//Reset menu - re-load the custom menu. Discards any changes made by user.
|
1489 |
$('#ws_reset_menu').click(function () {
|
1490 |
if (confirm('Undo all changes made in the current editing session?')){
|
1491 |
-
outputWpMenu(customMenu);
|
1492 |
}
|
1493 |
});
|
1494 |
-
|
1495 |
//Export menu - download the current menu as a file
|
1496 |
-
$('#export_dialog').dialog({
|
1497 |
autoOpen: false,
|
1498 |
closeText: ' ',
|
1499 |
modal: true,
|
1500 |
minHeight: 100
|
1501 |
});
|
1502 |
-
|
1503 |
$('#ws_export_menu').click(function(){
|
1504 |
var button = $(this);
|
1505 |
button.attr('disabled', 'disabled');
|
1506 |
button.val('Exporting...');
|
1507 |
-
|
1508 |
$('#export_complete_notice, #download_menu_button').hide();
|
1509 |
$('#export_progress_notice').show();
|
1510 |
$('#export_dialog').dialog('open');
|
1511 |
-
|
1512 |
//Encode and store the menu for download
|
1513 |
-
var
|
1514 |
-
|
1515 |
-
'format' : exportFormatString,
|
1516 |
-
'menu' : menu
|
1517 |
-
};
|
1518 |
-
exportData = $.toJSON(exportData);
|
1519 |
-
|
1520 |
$.post(
|
1521 |
-
adminAjaxUrl,
|
1522 |
{
|
1523 |
'data' : exportData,
|
1524 |
'action' : 'export_custom_menu',
|
1525 |
-
'_ajax_nonce' : exportMenuNonce
|
1526 |
},
|
1527 |
-
function(data
|
1528 |
button.val('Export');
|
1529 |
button.removeAttr('disabled');
|
1530 |
-
|
1531 |
if ( typeof data['error'] != 'undefined' ){
|
1532 |
$('#export_dialog').dialog('close');
|
1533 |
alert(data.error);
|
1534 |
}
|
1535 |
-
|
1536 |
if ( (typeof data['download_url'] != 'undefined') && data.download_url ){
|
1537 |
//window.location = data.download_url;
|
1538 |
$('#download_menu_button').attr('href', data.download_url);
|
@@ -1543,50 +2270,46 @@ $(document).ready(function(){
|
|
1543 |
'json'
|
1544 |
);
|
1545 |
});
|
1546 |
-
|
1547 |
$('#ws_cancel_export').click(function(){
|
1548 |
$('#export_dialog').dialog('close');
|
1549 |
});
|
1550 |
-
|
1551 |
$('#download_menu_button').click(function(){
|
1552 |
$('#export_dialog').dialog('close');
|
1553 |
});
|
1554 |
-
|
1555 |
//Import menu - upload an exported menu and show it in the editor
|
1556 |
-
$('#import_dialog').dialog({
|
1557 |
autoOpen: false,
|
1558 |
closeText: ' ',
|
1559 |
modal: true
|
1560 |
});
|
1561 |
-
|
1562 |
$('#ws_cancel_import').click(function(){
|
1563 |
$('#import_dialog').dialog('close');
|
1564 |
});
|
1565 |
-
|
1566 |
$('#ws_import_menu').click(function(){
|
1567 |
$('#import_progress_notice, #import_progress_notice2, #import_complete_notice').hide();
|
1568 |
$('#import_menu_form').resetForm();
|
1569 |
//The "Upload" button is disabled until the user selects a file
|
1570 |
-
$('#ws_start_import').attr('disabled', 'disabled');
|
1571 |
-
|
1572 |
-
$('#import_dialog
|
1573 |
-
|
1574 |
-
|
1575 |
-
})
|
1576 |
-
|
1577 |
$('#import_file_selector').change(function(){
|
1578 |
-
|
1579 |
-
$('#ws_start_import').removeAttr('disabled');
|
1580 |
-
} else {
|
1581 |
-
$('#ws_start_import').attr('disabled', 'disabled');
|
1582 |
-
};
|
1583 |
});
|
1584 |
-
|
1585 |
//AJAXify the upload form
|
1586 |
$('#import_menu_form').ajaxForm({
|
1587 |
dataType : 'json',
|
1588 |
-
beforeSubmit: function(formData
|
1589 |
-
|
1590 |
//Check if the user has selected a file
|
1591 |
for(var i = 0; i < formData.length; i++){
|
1592 |
if ( formData[i].name == 'menu' ){
|
@@ -1596,44 +2319,97 @@ $(document).ready(function(){
|
|
1596 |
}
|
1597 |
}
|
1598 |
}
|
1599 |
-
|
1600 |
-
$('#import_dialog
|
1601 |
$('#import_progress_notice').show();
|
1602 |
-
|
1603 |
$('#ws_start_import').attr('disabled', 'disabled');
|
|
|
1604 |
},
|
1605 |
success: function(data){
|
1606 |
-
|
|
|
1607 |
//Whoops, the user closed the dialog while the upload was in progress.
|
1608 |
//Discard the response silently.
|
1609 |
-
return;
|
1610 |
-
}
|
1611 |
-
|
1612 |
if ( typeof data['error'] != 'undefined' ){
|
1613 |
alert(data.error);
|
1614 |
//Let the user try again
|
1615 |
$('#import_menu_form').resetForm();
|
1616 |
-
|
1617 |
}
|
1618 |
$('#import_progress_notice').hide();
|
1619 |
-
|
1620 |
-
if ( (typeof data['
|
1621 |
//Whee, we got back a (seemingly) valid menu. A veritable miracle!
|
1622 |
//Lets load it into the editor.
|
1623 |
-
$('#import_progress_notice2').show();
|
1624 |
-
outputWpMenu(data.
|
1625 |
-
|
1626 |
//Display a success notice, then automatically close the window after a few moments
|
1627 |
$('#import_complete_notice').show();
|
1628 |
setTimeout((function(){
|
1629 |
//Close the import dialog
|
1630 |
$('#import_dialog').dialog('close');
|
1631 |
-
}), 500);
|
1632 |
}
|
1633 |
-
|
1634 |
}
|
1635 |
});
|
1636 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1637 |
//Flag closed hints as hidden by sending the appropriate AJAX request to the backend.
|
1638 |
$('.ws_hint_close').click(function() {
|
1639 |
var hint = $(this).parents('.ws_hint').first();
|
@@ -1647,11 +2423,53 @@ $(document).ready(function(){
|
|
1647 |
}
|
1648 |
);
|
1649 |
});
|
1650 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1651 |
//Finally, show the menu
|
1652 |
-
outputWpMenu(customMenu);
|
1653 |
});
|
1654 |
-
|
1655 |
})(jQuery);
|
1656 |
|
1657 |
//==============================================
|
@@ -1661,34 +2479,35 @@ $(document).ready(function(){
|
|
1661 |
jQuery(function($){
|
1662 |
var screenOptions = $('#ws-ame-screen-meta-contents');
|
1663 |
var checkbox = screenOptions.find('#ws-hide-advanced-settings');
|
1664 |
-
|
1665 |
-
if ( hideAdvancedSettings ){
|
1666 |
checkbox.attr('checked', 'checked');
|
1667 |
} else {
|
1668 |
checkbox.removeAttr('checked');
|
1669 |
}
|
1670 |
-
|
1671 |
//Update editor state when settings change
|
1672 |
checkbox.click(function(){
|
1673 |
-
hideAdvancedSettings = $(this).attr('checked'); //Using '$(this)' instead of 'checkbox' due to jQuery bugs
|
1674 |
-
|
1675 |
-
|
1676 |
-
|
|
|
1677 |
} else {
|
1678 |
-
|
1679 |
-
|
1680 |
}
|
1681 |
-
|
1682 |
$.post(
|
1683 |
-
adminAjaxUrl,
|
1684 |
{
|
1685 |
'action' : 'ws_ame_save_screen_options',
|
1686 |
-
'hide_advanced_settings' : hideAdvancedSettings?1:0,
|
1687 |
-
'_ajax_nonce' : hideAdvancedSettingsNonce
|
1688 |
}
|
1689 |
);
|
1690 |
});
|
1691 |
-
|
1692 |
//Move our options into the screen meta panel
|
1693 |
$('#adv-settings').empty().append(screenOptions.show());
|
1694 |
});
|
1 |
//(c) W-Shadow
|
2 |
|
3 |
+
/*global wsEditorData, defaultMenu, customMenu */
|
4 |
/** @namespace wsEditorData */
|
5 |
|
6 |
var wsIdCounter = 0;
|
7 |
|
8 |
+
var AmeCapabilityManager = (function(roles, users) {
|
9 |
+
var me = {};
|
10 |
+
users = users || {};
|
11 |
+
|
12 |
+
function parseActorString(actor) {
|
13 |
+
var separator = actor.indexOf(':');
|
14 |
+
if (separator == -1) {
|
15 |
+
throw {
|
16 |
+
name: 'InvalidActorException',
|
17 |
+
message: "Actor string does not contain a colon.",
|
18 |
+
value: actor
|
19 |
+
};
|
20 |
+
}
|
21 |
+
|
22 |
+
return {
|
23 |
+
'type' : actor.substring(0, separator),
|
24 |
+
'id' : actor.substring(separator + 1)
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
me.hasCap = function(actor, capability, context) {
|
29 |
+
context = context || {};
|
30 |
+
var actorData = parseActorString(actor);
|
31 |
+
|
32 |
+
//Super admins have access to everything, unless specifically denied.
|
33 |
+
if ( actor == 'special:super_admin' ) {
|
34 |
+
return (capability != 'do_not_allow');
|
35 |
+
}
|
36 |
+
|
37 |
+
if (actorData.type == 'role') {
|
38 |
+
return me.roleHasCap(actorData.id, capability);
|
39 |
+
} else if (actorData.type == 'user') {
|
40 |
+
return me.userHasCap(actorData.id, capability, context);
|
41 |
+
}
|
42 |
+
|
43 |
+
throw {
|
44 |
+
name: 'InvalidActorTypeException',
|
45 |
+
message: "The specified actor type is not supported",
|
46 |
+
value: actor,
|
47 |
+
'actorType': actorData.type
|
48 |
+
};
|
49 |
+
};
|
50 |
+
|
51 |
+
me.roleHasCap = function(roleId, capability) {
|
52 |
+
if (!roles.hasOwnProperty(roleId)) {
|
53 |
+
throw {
|
54 |
+
name: 'UnknownRoleException',
|
55 |
+
message: 'Can not check capabilities for an unknown role',
|
56 |
+
value: roleId,
|
57 |
+
requireCapability: capability
|
58 |
+
};
|
59 |
+
}
|
60 |
+
|
61 |
+
var role = roles[roleId];
|
62 |
+
if ( role.capabilities.hasOwnProperty(capability) ) {
|
63 |
+
return role.capabilities[capability];
|
64 |
+
} else if (roleId == capability) {
|
65 |
+
return true;
|
66 |
+
}
|
67 |
+
return false;
|
68 |
+
};
|
69 |
+
|
70 |
+
me.userHasCap = function(login, capability, context) {
|
71 |
+
context = context || {};
|
72 |
+
if (!users.hasOwnProperty(login)) {
|
73 |
+
throw {
|
74 |
+
name: 'UnknownUserException',
|
75 |
+
message: 'Can not check capabilities for an unknown user',
|
76 |
+
value: login,
|
77 |
+
requireCapability: capability
|
78 |
+
};
|
79 |
+
}
|
80 |
+
|
81 |
+
var user = users[login];
|
82 |
+
if ( user.capabilities.hasOwnProperty(capability) ) {
|
83 |
+
return user.capabilities[capability];
|
84 |
+
} else {
|
85 |
+
//Super Admins have all capabilities, except those explicitly denied.
|
86 |
+
//We also need to check if the Super Admin actor is allowed in this context.
|
87 |
+
if (user.is_super_admin ) {
|
88 |
+
if (context.hasOwnProperty('special:super_admin')) {
|
89 |
+
return context['special:super_admin'];
|
90 |
+
}
|
91 |
+
return (capability != 'do_not_allow');
|
92 |
+
}
|
93 |
+
|
94 |
+
//Check if any of the user's roles have the capability.
|
95 |
+
for(var index = 0; index < user.roles.length; index++) {
|
96 |
+
var roleId = user.roles[index];
|
97 |
+
|
98 |
+
//Skip roles that are disabled in this context (i.e. via grant_access).
|
99 |
+
if (context.hasOwnProperty('role:' + roleId) && !context['role:' + roleId]) {
|
100 |
+
continue;
|
101 |
+
}
|
102 |
+
|
103 |
+
if (me.roleHasCap(roleId, capability)) {
|
104 |
+
return true;
|
105 |
+
}
|
106 |
+
}
|
107 |
+
}
|
108 |
+
|
109 |
+
return false;
|
110 |
+
};
|
111 |
+
|
112 |
+
me.roleExists = function(roleId) {
|
113 |
+
return roles.hasOwnProperty(roleId);
|
114 |
+
};
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Compare the specificity of two actors.
|
118 |
+
*
|
119 |
+
* Returns 1 if the first actor is more specific than the second, 0 if they're both
|
120 |
+
* equally specific, and -1 if the second actor is more specific.
|
121 |
+
*
|
122 |
+
* @param {String} actor1
|
123 |
+
* @param {String} actor2
|
124 |
+
* @return {Number}
|
125 |
+
*/
|
126 |
+
me.compareActorSpecificity = function(actor1, actor2) {
|
127 |
+
var delta = me.getActorSpecificity(actor1) - me.getActorSpecificity(actor2);
|
128 |
+
if (delta !== 0) {
|
129 |
+
delta = (delta > 0) ? 1 : -1;
|
130 |
+
}
|
131 |
+
return delta;
|
132 |
+
};
|
133 |
+
|
134 |
+
me.getActorSpecificity = function(actorString) {
|
135 |
+
var actor = parseActorString(actorString);
|
136 |
+
var specificity = 0;
|
137 |
+
switch(actor.type) {
|
138 |
+
case 'role':
|
139 |
+
specificity = 1;
|
140 |
+
break;
|
141 |
+
case 'special':
|
142 |
+
specificity = 2;
|
143 |
+
break;
|
144 |
+
case 'user':
|
145 |
+
specificity = 10;
|
146 |
+
break;
|
147 |
+
}
|
148 |
+
return specificity;
|
149 |
+
};
|
150 |
+
|
151 |
+
|
152 |
+
return me;
|
153 |
+
})(wsEditorData.roles, wsEditorData.users);
|
154 |
+
|
155 |
(function ($){
|
156 |
+
|
157 |
+
var selectedActor = null;
|
158 |
+
|
159 |
+
var itemTemplates = {
|
160 |
+
templates: wsEditorData.itemTemplates,
|
161 |
+
|
162 |
+
getTemplateById: function(templateId) {
|
163 |
+
if (wsEditorData.itemTemplates.hasOwnProperty(templateId)) {
|
164 |
+
return wsEditorData.itemTemplates[templateId];
|
165 |
+
} else if ((templateId == '') || (templateId == 'custom')) {
|
166 |
+
return wsEditorData.customItemTemplate;
|
167 |
+
}
|
168 |
+
return null;
|
169 |
+
},
|
170 |
+
|
171 |
+
getDefaults: function (templateId) {
|
172 |
+
var template = this.getTemplateById(templateId);
|
173 |
+
if (template) {
|
174 |
+
return template.defaults;
|
175 |
+
} else {
|
176 |
+
return null;
|
177 |
+
}
|
178 |
+
},
|
179 |
+
|
180 |
+
getDefaultValue: function (templateId, fieldName) {
|
181 |
+
if (fieldName == 'template_id') {
|
182 |
+
return null;
|
183 |
+
}
|
184 |
+
|
185 |
+
var defaults = this.getDefaults(templateId);
|
186 |
+
if (defaults && (typeof defaults[fieldName] != 'undefined')) {
|
187 |
+
return defaults[fieldName];
|
188 |
+
}
|
189 |
+
return null;
|
190 |
+
},
|
191 |
+
|
192 |
+
hasDefaultValue: function(templateId, fieldName) {
|
193 |
+
return (this.getDefaultValue(templateId, fieldName) !== null);
|
194 |
+
}
|
195 |
+
};
|
196 |
+
|
197 |
+
/**
|
198 |
+
* Set an input field to a value. The only difference from jQuery.val() is that
|
199 |
+
* setting a checkbox to true/false will check/clear it.
|
200 |
+
*
|
201 |
+
* @param input
|
202 |
+
* @param value
|
203 |
+
*/
|
204 |
+
function setInputValue(input, value) {
|
205 |
+
if (input.attr('type') == 'checkbox'){
|
206 |
+
if (value){
|
207 |
+
input.attr('checked', 'checked');
|
208 |
+
} else {
|
209 |
+
input.removeAttr('checked');
|
210 |
+
}
|
211 |
+
} else {
|
212 |
+
input.val(value);
|
213 |
+
}
|
214 |
+
}
|
215 |
+
|
216 |
+
/**
|
217 |
+
* Get the value of an input field. The only difference from jQuery.val() is that
|
218 |
+
* checked/unchecked checkboxes will return true/false.
|
219 |
+
*
|
220 |
+
* @param input
|
221 |
+
* @return {*}
|
222 |
+
*/
|
223 |
+
function getInputValue(input) {
|
224 |
+
if (input.attr('type') == 'checkbox'){
|
225 |
+
return input.is(':checked');
|
226 |
+
}
|
227 |
+
return input.val();
|
228 |
+
}
|
229 |
+
|
230 |
+
|
231 |
/*
|
232 |
* Utility function for generating pseudo-random alphanumeric menu IDs.
|
233 |
* Rationale: Simpler than atomically auto-incrementing or globally unique IDs.
|
234 |
*/
|
235 |
+
function randomMenuId(prefix, size){
|
236 |
+
prefix = (typeof prefix == 'undefined') ? 'custom_item_' : prefix;
|
237 |
+
size = (typeof size == 'undefined') ? 5 : size;
|
238 |
+
|
239 |
+
var suffix = "";
|
|
|
240 |
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
241 |
|
242 |
+
for( var i=0; i < size; i++ ) {
|
243 |
+
suffix += possible.charAt(Math.floor(Math.random() * possible.length));
|
244 |
+
}
|
245 |
|
246 |
+
return prefix + suffix;
|
247 |
}
|
248 |
+
|
249 |
function outputWpMenu(menu){
|
250 |
+
var menuCopy = $.extend(true, {}, menu);
|
251 |
+
var menuBox = $('#ws_menu_box');
|
252 |
+
|
253 |
//Remove the current menu data
|
254 |
+
menuBox.empty();
|
255 |
$('#ws_submenu_box').empty();
|
256 |
+
|
|
|
|
|
257 |
//Display the new menu
|
258 |
var i = 0;
|
259 |
+
for (var filename in menuCopy){
|
260 |
+
if (!menuCopy.hasOwnProperty(filename)){
|
261 |
+
continue;
|
262 |
+
}
|
263 |
+
outputTopMenu(menuCopy[filename]);
|
264 |
i++;
|
265 |
}
|
266 |
+
|
267 |
//Automatically select the first top-level menu
|
268 |
+
menuBox.find('.ws_menu:first').click();
|
269 |
}
|
270 |
|
271 |
/*
|
272 |
* Create edit widgets for a top-level menu and its submenus and append them all to the DOM.
|
273 |
*
|
274 |
+
* Inputs :
|
275 |
* menu - an object containing menu data
|
276 |
* afterNode - if specified, the new menu widget will be inserted after this node. Otherwise,
|
277 |
* it will be added to the end of the list.
|
281 |
function outputTopMenu(menu, afterNode){
|
282 |
//Create a container for menu items, even if there are none
|
283 |
var submenu = buildSubmenu(menu.items);
|
284 |
+
|
285 |
//Create the menu widget
|
286 |
+
var menu_obj = buildMenuItem(menu, true);
|
287 |
menu_obj.data('submenu_id', submenu.attr('id'));
|
288 |
+
submenu.data('parent_menu_id', menu_obj.attr('id'));
|
289 |
+
|
290 |
//Display
|
291 |
submenu.appendTo('#ws_submenu_box');
|
292 |
+
updateItemEditor(menu_obj);
|
293 |
+
if ( (typeof afterNode != 'undefined') && (afterNode != null) ){
|
294 |
$(afterNode).after(menu_obj);
|
295 |
} else {
|
296 |
menu_obj.appendTo('#ws_menu_box');
|
297 |
}
|
298 |
+
|
299 |
return {
|
300 |
'menu' : menu_obj,
|
301 |
'submenu' : submenu
|
302 |
};
|
303 |
}
|
304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
/*
|
306 |
* Create and populate a submenu container.
|
307 |
*/
|
308 |
function buildSubmenu(items){
|
309 |
//Create a container for menu items, even if there are none
|
310 |
+
var submenu = $('<div class="ws_submenu" style="display:none;"></div>');
|
311 |
submenu.attr('id', 'ws-submenu-'+(wsIdCounter++));
|
312 |
+
|
313 |
+
//Only show menus that have items.
|
314 |
//Skip arrays (with a length) because filled menus are encoded as custom objects.
|
315 |
var entry = null;
|
316 |
+
if (items) {
|
317 |
+
$.each(items, function(index, item) {
|
318 |
+
entry = buildMenuItem(item, false);
|
319 |
if ( entry ){
|
320 |
+
updateItemEditor(entry);
|
321 |
submenu.append(entry);
|
322 |
}
|
323 |
+
});
|
324 |
}
|
325 |
+
|
326 |
//Make the submenu sortable
|
327 |
makeBoxSortable(submenu);
|
328 |
+
|
329 |
return submenu;
|
330 |
}
|
331 |
|
332 |
+
/**
|
333 |
+
* Create an edit widget for a menu item.
|
334 |
+
*
|
335 |
+
* @param {Object} itemData
|
336 |
+
* @param {Boolean} [isTopLevel] Specify if this is a top-level menu or a sub-menu item. Defaults to false (= sub-item).
|
337 |
+
* @return {*} The created widget as a jQuery object.
|
338 |
*/
|
339 |
+
function buildMenuItem(itemData, isTopLevel) {
|
340 |
+
isTopLevel = (typeof isTopLevel == 'undefined') ? false : isTopLevel;
|
341 |
+
|
342 |
+
//Create the menu HTML
|
343 |
+
var item = $('<div></div>')
|
344 |
+
.attr('class', "ws_container")
|
345 |
+
.attr('id', 'ws-menu-item-' + (wsIdCounter++))
|
346 |
+
.data('menu_item', itemData)
|
347 |
.data('field_editors_created', false);
|
348 |
+
|
349 |
+
item.addClass(isTopLevel ? 'ws_menu' : 'ws_item');
|
350 |
+
if ( itemData.separator ) {
|
351 |
+
item.addClass('ws_menu_separator');
|
352 |
+
}
|
353 |
+
|
354 |
//Add a header and a container for property editors (to improve performance
|
355 |
//the editors themselves are created later, when the user tries to access them
|
356 |
//for the first time).
|
357 |
var contents = [];
|
358 |
contents.push(
|
359 |
'<div class="ws_item_head">',
|
360 |
+
itemData.separator ? '' : '<a class="ws_edit_link"> </a><div class="ws_flag_container"> </div>',
|
361 |
+
'<input type="checkbox" class="ws_actor_access_checkbox">',
|
362 |
'<span class="ws_item_title">',
|
363 |
+
((itemData.menu_title != null) ? itemData.menu_title : itemData.defaults.menu_title),
|
364 |
' </span>',
|
365 |
+
|
366 |
'</div>',
|
367 |
'<div class="ws_editbox" style="display: none;"></div>'
|
368 |
);
|
369 |
item.append(contents.join(''));
|
370 |
+
|
371 |
//Apply flags based on the item's state
|
372 |
+
var flags = ['hidden', 'unused', 'custom'];
|
373 |
+
for (var i = 0; i < flags.length; i++) {
|
374 |
+
setMenuFlag(item, flags[i], getFieldValue(itemData, flags[i], false));
|
|
|
|
|
|
|
|
|
|
|
375 |
}
|
376 |
+
|
377 |
+
if ( isTopLevel && !itemData.separator ){
|
378 |
+
//Allow the user to drag menu items to top-level menus
|
379 |
+
item.droppable({
|
380 |
+
'hoverClass' : 'ws_menu_drop_hover',
|
381 |
+
|
382 |
+
'accept' : (function(thing){
|
383 |
+
return thing.hasClass('ws_item');
|
384 |
+
}),
|
385 |
+
|
386 |
+
'drop' : (function(event, ui){
|
387 |
+
var droppedItemData = readItemState(ui.draggable);
|
388 |
+
var new_item = buildMenuItem(droppedItemData, false);
|
389 |
+
|
390 |
+
var sourceSubmenu = ui.draggable.parent();
|
391 |
+
var submenu = $('#' + item.data('submenu_id'));
|
392 |
+
submenu.append(new_item);
|
393 |
+
|
394 |
+
if ( !event.ctrlKey ) {
|
395 |
+
ui.draggable.remove();
|
396 |
+
}
|
397 |
+
|
398 |
+
updateItemEditor(new_item);
|
399 |
+
|
400 |
+
//Moving an item can change aggregate menu permissions. Update the UI accordingly.
|
401 |
+
updateParentAccessUi(submenu);
|
402 |
+
updateParentAccessUi(sourceSubmenu);
|
403 |
+
})
|
404 |
+
});
|
405 |
}
|
406 |
+
|
407 |
return item;
|
408 |
}
|
409 |
|
410 |
+
function jsTrim(str){
|
411 |
+
return str.replace(/^\s+|\s+$/g, "");
|
412 |
+
}
|
413 |
+
|
414 |
+
//Editor field spec template.
|
415 |
+
var baseField = {
|
416 |
+
caption : '[No caption]',
|
417 |
+
standardCaption : true,
|
418 |
+
advanced : false,
|
419 |
+
type : 'text',
|
420 |
+
defaultValue: '',
|
421 |
+
onlyForTopMenus: false,
|
422 |
+
addDropdown : false,
|
423 |
+
visible: true,
|
424 |
+
|
425 |
+
write: null,
|
426 |
+
display: null
|
427 |
+
};
|
428 |
+
|
429 |
/*
|
430 |
* List of all menu fields that have an associated editor
|
431 |
+
*/
|
432 |
var knownMenuFields = {
|
433 |
+
'menu_title' : $.extend({}, baseField, {
|
434 |
caption : 'Menu title',
|
435 |
+
display: function(menuItem, displayValue, input, containerNode) {
|
436 |
+
//Update the header as well.
|
437 |
+
containerNode.find('.ws_item_title').html(displayValue);
|
438 |
+
return displayValue;
|
439 |
+
},
|
440 |
+
write: function(menuItem, value, input, containerNode) {
|
441 |
+
menuItem.menu_title = value;
|
442 |
+
containerNode.find('.ws_item_title').html(input.val() + ' ');
|
443 |
+
}
|
444 |
+
}),
|
445 |
+
|
446 |
+
'template_id' : $.extend({}, baseField, {
|
447 |
+
caption : 'Target page',
|
448 |
+
type : 'select',
|
449 |
+
options : (function(){
|
450 |
+
//Generate name => id mappings for all item templates + the special "Custom" template.
|
451 |
+
var itemTemplateIds = {};
|
452 |
+
itemTemplateIds[wsEditorData.customItemTemplate.name] = '';
|
453 |
+
for (var template_id in wsEditorData.itemTemplates) {
|
454 |
+
if (wsEditorData.itemTemplates.hasOwnProperty(template_id)) {
|
455 |
+
itemTemplateIds[wsEditorData.itemTemplates[template_id].name] = template_id;
|
456 |
+
}
|
457 |
+
}
|
458 |
+
return itemTemplateIds;
|
459 |
+
})(),
|
460 |
+
|
461 |
+
write: function(menuItem, value, input, containerNode) {
|
462 |
+
menuItem.template_id = value;
|
463 |
+
menuItem.defaults = itemTemplates.getDefaults(menuItem.template_id);
|
464 |
+
menuItem.custom = (menuItem.template_id == '');
|
465 |
+
|
466 |
+
// The file/URL of non-custom items is read-only and equal to the default
|
467 |
+
// value. Rationale: simplifies menu generation, prevents some user mistakes.
|
468 |
+
if (menuItem.template_id !== '') {
|
469 |
+
menuItem.file = null;
|
470 |
+
}
|
471 |
+
|
472 |
+
// The new template might not have default values for some of the fields
|
473 |
+
// currently set to null (= "default"). In those cases, we need to make
|
474 |
+
// the current values explicit.
|
475 |
+
containerNode.find('.ws_edit_field').each(function(index, field){
|
476 |
+
field = $(field);
|
477 |
+
var fieldName = field.data('field_name');
|
478 |
+
var isSetToDefault = (menuItem[fieldName] === null);
|
479 |
+
var hasDefaultValue = itemTemplates.hasDefaultValue(menuItem.template_id, fieldName);
|
480 |
+
|
481 |
+
if (isSetToDefault && !hasDefaultValue) {
|
482 |
+
menuItem[fieldName] = getInputValue(field.find('.ws_field_value'));
|
483 |
+
}
|
484 |
+
});
|
485 |
+
}
|
486 |
+
}),
|
487 |
+
|
488 |
+
'file' : $.extend({}, baseField, {
|
489 |
+
caption: 'URL',
|
490 |
+
display: function(menuItem, displayValue, input) {
|
491 |
+
// The URL/file field is read-only for default menus. Also, since the "file"
|
492 |
+
// field is usually set to a page slug or plugin filename for plugin/hook pages,
|
493 |
+
// we display the dynamically generated "url" field here (i.e. the actual URL) instead.
|
494 |
+
if (menuItem.template_id !== '') {
|
495 |
+
input.attr('readonly', 'readonly');
|
496 |
+
displayValue = itemTemplates.getDefaultValue(menuItem.template_id, 'url');
|
497 |
+
} else {
|
498 |
+
input.removeAttr('readonly');
|
499 |
+
}
|
500 |
+
return displayValue;
|
501 |
+
},
|
502 |
+
|
503 |
+
write: function(menuItem, value) {
|
504 |
+
// A menu must always have a non-empty URL. If the user deletes the current value,
|
505 |
+
// reset it to the old value.
|
506 |
+
if (value === '') {
|
507 |
+
value = menuItem.file;
|
508 |
+
}
|
509 |
+
// Default menus always point to the default file/URL.
|
510 |
+
if (menuItem.template_id !== '') {
|
511 |
+
value = null;
|
512 |
+
}
|
513 |
+
menuItem.file = value;
|
514 |
+
}
|
515 |
+
}),
|
516 |
+
|
517 |
+
'access_level' : $.extend({}, baseField, {
|
518 |
+
caption: 'Permissions',
|
519 |
+
defaultValue: 'read',
|
520 |
+
type: 'access_editor',
|
521 |
+
visible: false, //Will be set to visible only in Pro version.
|
522 |
+
|
523 |
+
display: function(menuItem) {
|
524 |
+
//Permissions display is a little complicated and could use improvement.
|
525 |
+
var requiredCap = getFieldValue(menuItem, 'access_level', '');
|
526 |
+
var extraCap = getFieldValue(menuItem, 'extra_capability', '');
|
527 |
+
|
528 |
+
var displayValue = (menuItem.template_id === '') ? '< Custom >' : requiredCap;
|
529 |
+
if (extraCap !== '') {
|
530 |
+
if (menuItem.template_id === '') {
|
531 |
+
displayValue = extraCap;
|
532 |
+
} else {
|
533 |
+
displayValue = displayValue + '+' + extraCap;
|
534 |
+
}
|
535 |
+
}
|
536 |
+
|
537 |
+
return displayValue;
|
538 |
+
},
|
539 |
+
|
540 |
+
write: function(menuItem) {
|
541 |
+
//The required capability can't be directly edited and always equals the default.
|
542 |
+
menuItem.access_level = null;
|
543 |
+
}
|
544 |
+
}),
|
545 |
+
|
546 |
+
'extra_capability' : $.extend({}, baseField, {
|
547 |
caption: 'Required capability',
|
|
|
|
|
|
|
548 |
defaultValue: 'read',
|
549 |
+
type: 'text',
|
550 |
+
addDropdown: 'ws_cap_selector',
|
551 |
+
|
552 |
+
display: function(menuItem) {
|
553 |
+
//Permissions display is a little complicated and could use improvement.
|
554 |
+
var requiredCap = getFieldValue(menuItem, 'access_level', '');
|
555 |
+
var extraCap = getFieldValue(menuItem, 'extra_capability', '');
|
556 |
+
|
557 |
+
var displayValue = extraCap;
|
558 |
+
if ((extraCap === '') || (extraCap === null)) {
|
559 |
+
displayValue = requiredCap;
|
560 |
+
}
|
561 |
+
|
562 |
+
return displayValue;
|
563 |
+
},
|
564 |
+
|
565 |
+
write: function(menuItem, value) {
|
566 |
+
value = jsTrim(value);
|
567 |
+
|
568 |
+
//Reset to default if the user clears the input.
|
569 |
+
if (value === '') {
|
570 |
+
menuItem.extra_capability = null;
|
571 |
+
return;
|
572 |
+
}
|
573 |
+
|
574 |
+
//It would be redundant to set an extra_capability that it matches access_level.
|
575 |
+
var requiredCap = getFieldValue(menuItem, 'access_level', '');
|
576 |
+
var extraCap = getFieldValue(menuItem, 'extra_capability', '');
|
577 |
+
if (extraCap === '' && value === requiredCap) {
|
578 |
+
return;
|
579 |
+
}
|
580 |
+
|
581 |
+
menuItem.extra_capability = value;
|
582 |
+
}
|
583 |
+
}),
|
584 |
+
|
585 |
+
'page_title' : $.extend({}, baseField, {
|
586 |
caption: "Window title",
|
587 |
standardCaption : true,
|
588 |
+
advanced : true
|
589 |
+
}),
|
590 |
+
|
591 |
+
'open_in' : $.extend({}, baseField, {
|
|
|
|
|
592 |
caption: 'Open in',
|
|
|
593 |
advanced : true,
|
594 |
type : 'select',
|
595 |
options : {
|
599 |
},
|
600 |
defaultValue: 'same_window',
|
601 |
visible: false
|
602 |
+
}),
|
603 |
+
|
604 |
+
'css_class' : $.extend({}, baseField, {
|
605 |
caption: 'CSS classes',
|
|
|
606 |
advanced : true,
|
607 |
+
onlyForTopMenus: true
|
608 |
+
}),
|
609 |
+
|
610 |
+
'icon_url' : $.extend({}, baseField, {
|
|
|
611 |
caption: 'Icon URL',
|
|
|
|
|
612 |
type : 'icon_selector',
|
613 |
+
advanced : true,
|
614 |
defaultValue: 'div',
|
615 |
+
onlyForTopMenus: true,
|
616 |
+
|
617 |
+
display: function(menuItem, displayValue, input) {
|
618 |
+
//Display the current icon in the selector.
|
619 |
+
var cssClass = getFieldValue(menuItem, 'css_class', '');
|
620 |
+
var iconUrl = getFieldValue(menuItem, 'icon_url', '');
|
621 |
+
|
622 |
+
var selectButton = input.closest('.ws_edit_field').find('.ws_select_icon');
|
623 |
+
var cssIcon = selectButton.find('.icon16');
|
624 |
+
var imageIcon = selectButton.find('img');
|
625 |
+
|
626 |
+
var matches = cssClass.match(/\bmenu-icon-([^\s]+)\b/);
|
627 |
+
//Icon URL take precedence over icon class.
|
628 |
+
if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' ) {
|
629 |
+
cssIcon.hide();
|
630 |
+
imageIcon.prop('src', iconUrl).show();
|
631 |
+
} else if ( matches ) {
|
632 |
+
imageIcon.hide();
|
633 |
+
cssIcon.removeClass().addClass('icon16 icon-' + matches[1]).show();
|
634 |
+
} else {
|
635 |
+
//This menu has no icon at all. This is actually a valid state
|
636 |
+
//and WordPress will display a menu like that correctly.
|
637 |
+
imageIcon.hide();
|
638 |
+
cssIcon.removeClass().addClass('icon16').show();
|
639 |
+
}
|
640 |
+
|
641 |
+
return displayValue;
|
642 |
+
}
|
643 |
+
}),
|
644 |
+
|
645 |
+
'hookname' : $.extend({}, baseField, {
|
646 |
caption: 'Hook name',
|
|
|
647 |
advanced : true,
|
648 |
+
onlyForTopMenus: true
|
649 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
650 |
};
|
651 |
|
652 |
/*
|
653 |
* Create editors for the visible fields of a menu entry and append them to the specified node.
|
654 |
*/
|
655 |
+
function buildEditboxFields(fieldContainer, entry, isTopLevel){
|
656 |
+
isTopLevel = (typeof isTopLevel == 'undefined') ? false : isTopLevel;
|
657 |
+
|
658 |
+
var basicFields = $('<div class="ws_edit_panel ws_basic"></div>').appendTo(fieldContainer);
|
659 |
+
var advancedFields = $('<div class="ws_edit_panel ws_advanced"></div>').appendTo(fieldContainer);
|
660 |
+
|
661 |
+
if ( wsEditorData.hideAdvancedSettings ){
|
662 |
advancedFields.css('display', 'none');
|
663 |
}
|
664 |
+
|
665 |
+
for (var field_name in knownMenuFields){
|
666 |
+
if (!knownMenuFields.hasOwnProperty(field_name)) {
|
667 |
+
continue;
|
668 |
+
}
|
669 |
+
|
670 |
+
var fieldSpec = knownMenuFields[field_name];
|
671 |
+
if (fieldSpec.onlyForTopMenus && !isTopLevel) {
|
672 |
continue;
|
673 |
}
|
674 |
+
|
675 |
+
var field = buildEditboxField(entry, field_name, fieldSpec);
|
676 |
if (field){
|
677 |
+
if (fieldSpec.advanced){
|
678 |
advancedFields.append(field);
|
679 |
} else {
|
680 |
basicFields.append(field);
|
681 |
}
|
|
|
|
|
|
|
|
|
682 |
}
|
683 |
}
|
684 |
+
|
685 |
//Add a link that shows/hides advanced fields
|
686 |
+
fieldContainer.append(
|
687 |
'<div class="ws_toggle_container"><a href="#" class="ws_toggle_advanced_fields"'+
|
688 |
+
(wsEditorData.hideAdvancedSettings ? '' : ' style="display:none;"')+'>'+
|
689 |
+
(wsEditorData.hideAdvancedSettings ? wsEditorData.captionShowAdvanced : wsEditorData.captionHideAdvanced)
|
690 |
+'</a></div>'
|
691 |
);
|
692 |
}
|
698 |
if (typeof entry[field_name] === 'undefined') {
|
699 |
return null; //skip fields this entry doesn't have
|
700 |
}
|
701 |
+
|
702 |
+
//Build a form field of the appropriate type
|
|
|
|
|
|
|
703 |
var inputBox = null;
|
704 |
var basicTextField = '<input type="text" class="ws_field_value">';
|
705 |
+
//noinspection FallthroughInSwitchStatementJS
|
706 |
switch(field_settings.type){
|
707 |
case 'select':
|
708 |
inputBox = $('<select class="ws_field_value">');
|
709 |
var option = null;
|
710 |
for( var optionTitle in field_settings.options ){
|
711 |
+
if (!field_settings.options.hasOwnProperty(optionTitle)) {
|
712 |
+
continue;
|
713 |
+
}
|
714 |
option = $('<option>')
|
715 |
.val(field_settings.options[optionTitle])
|
716 |
.text(optionTitle);
|
|
|
|
|
|
|
717 |
option.appendTo(inputBox);
|
718 |
}
|
719 |
break;
|
720 |
+
|
721 |
case 'checkbox':
|
722 |
+
inputBox = $('<label><input type="checkbox" class="ws_field_value"> '+
|
723 |
field_settings.caption+'</label>'
|
724 |
);
|
725 |
break;
|
726 |
|
727 |
+
case 'access_editor':
|
728 |
+
inputBox = $('<input type="text" class="ws_field_value" readonly="readonly">')
|
729 |
+
.add('<input type="button" class="button ws_launch_access_editor" value="Edit...">');
|
730 |
+
break;
|
731 |
+
|
732 |
case 'icon_selector':
|
733 |
+
inputBox = $(basicTextField)
|
734 |
.add('<button class="button ws_select_icon" title="Select icon"><div class="icon16 icon-settings"></div><img src="" style="display:none;"></button>');
|
735 |
break;
|
736 |
+
|
737 |
+
case 'text': //Intentional fall-through.
|
738 |
default:
|
739 |
+
inputBox = $(basicTextField);
|
740 |
}
|
741 |
+
|
742 |
+
|
743 |
var className = "ws_edit_field ws_edit_field-"+field_name;
|
744 |
+
if (field_settings.addDropdown){
|
|
|
|
|
|
|
|
|
|
|
745 |
className += ' ws_has_dropdown';
|
746 |
}
|
747 |
+
|
748 |
+
var editField = $('<div>' + (field_settings.standardCaption ? (field_settings.caption+'<br>') : '') + '</div>')
|
749 |
.attr('class', className)
|
750 |
.append(inputBox);
|
751 |
+
|
752 |
+
if (field_settings.addDropdown) {
|
753 |
//Add a dropdown button
|
754 |
+
var dropdownId = field_settings.addDropdown;
|
|
|
|
|
|
|
755 |
editField.append(
|
756 |
$('<input type="button" value="▼">')
|
757 |
.addClass('button ws_dropdown_button')
|
759 |
.data('dropdownId', dropdownId)
|
760 |
);
|
761 |
}
|
762 |
+
|
763 |
editField
|
764 |
+
.append('<img src="' + wsEditorData.imagesUrl + '/transparent16.png" class="ws_reset_button" title="Reset to default value"> </img>')
|
765 |
+
.data('field_name', field_name);
|
766 |
+
|
|
|
767 |
if ( !field_settings.visible ){
|
768 |
editField.css('display', 'none');
|
769 |
}
|
770 |
+
|
771 |
+
return editField;
|
772 |
+
}
|
773 |
+
|
774 |
+
/**
|
775 |
+
* Update the UI elements that that indicate whether the currently selected
|
776 |
+
* actor can access a menu item.
|
777 |
+
*
|
778 |
+
* @param containerNode
|
779 |
+
*/
|
780 |
+
function updateActorAccessUi(containerNode) {
|
781 |
+
//Update the permissions checkbox & UI
|
782 |
+
if (selectedActor != null) {
|
783 |
+
var menuItem = containerNode.data('menu_item');
|
784 |
+
var hasAccess = actorCanAccessMenu(menuItem, selectedActor);
|
785 |
+
|
786 |
+
var checkbox = containerNode.find('.ws_actor_access_checkbox');
|
787 |
+
checkbox.prop('checked', hasAccess);
|
788 |
+
|
789 |
+
//Display the checkbox differently if some items of this menu are hidden and some are visible,
|
790 |
+
//or if their permissions don't match this menu's permissions.
|
791 |
+
var submenuId = containerNode.data('submenu_id');
|
792 |
+
var submenuItems = submenuId ? $('#' + submenuId).children('.ws_container') : [];
|
793 |
+
if (!submenuId || submenuItems.length === 0) {
|
794 |
+
//This menu doesn't contain any items.
|
795 |
+
checkbox.prop('indeterminate', false);
|
796 |
+
} else {
|
797 |
+
var differentPermissions = false;
|
798 |
+
submenuItems.each(function() {
|
799 |
+
var item = $(this).data('menu_item');
|
800 |
+
if ( !item ) { //Skip placeholder items created by drag & drop operations.
|
801 |
+
return true;
|
802 |
+
}
|
803 |
+
var hasSubmenuAccess = actorCanAccessMenu(item, selectedActor);
|
804 |
+
if (hasSubmenuAccess !== hasAccess) {
|
805 |
+
differentPermissions = true;
|
806 |
+
return false;
|
807 |
+
}
|
808 |
+
return true;
|
809 |
+
});
|
810 |
+
|
811 |
+
checkbox.prop('indeterminate', differentPermissions);
|
812 |
+
}
|
813 |
+
|
814 |
+
containerNode.toggleClass('ws_is_hidden_for_actor', !hasAccess);
|
815 |
+
} else {
|
816 |
+
containerNode.removeClass('ws_is_hidden_for_actor');
|
817 |
+
}
|
818 |
}
|
819 |
|
820 |
+
/**
|
821 |
+
* Like updateActorAccessUi() except it updates the specified menu's parent, not the menu itself.
|
822 |
+
* If the menu has no parent (i.e. it's a top-level menu), this function does nothing.
|
823 |
+
*
|
824 |
+
* @param containerNode Either a menu item or a submenu container.
|
825 |
+
*/
|
826 |
+
function updateParentAccessUi(containerNode) {
|
827 |
+
var submenu;
|
828 |
+
if ( containerNode.is('.ws_submenu') ) {
|
829 |
+
submenu = containerNode;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
830 |
} else {
|
831 |
+
submenu = containerNode.parent();
|
832 |
+
}
|
833 |
+
|
834 |
+
var parentId = submenu.data('parent_menu_id');
|
835 |
+
if (parentId) {
|
836 |
+
updateActorAccessUi($('#' + parentId));
|
837 |
}
|
838 |
}
|
839 |
|
840 |
+
/**
|
841 |
+
* Update an edit widget with the current menu item settings.
|
842 |
+
*
|
843 |
+
* @param containerNode
|
844 |
*/
|
845 |
+
function updateItemEditor(containerNode) {
|
846 |
+
var menuItem = containerNode.data('menu_item');
|
847 |
+
|
848 |
+
//Apply flags based on the item's state.
|
849 |
+
var flags = ['hidden', 'unused', 'custom'];
|
850 |
+
for (var i = 0; i < flags.length; i++) {
|
851 |
+
setMenuFlag(containerNode, flags[i], getFieldValue(menuItem, flags[i], false));
|
|
|
852 |
}
|
853 |
+
|
854 |
+
//Update the permissions checkbox & other actor-specific UI
|
855 |
+
updateActorAccessUi(containerNode);
|
856 |
+
|
857 |
+
//Update all input fields with the current values.
|
858 |
+
containerNode.find('.ws_edit_field').each(function(index, field) {
|
859 |
+
field = $(field);
|
860 |
+
var fieldName = field.data('field_name');
|
861 |
+
var input = field.find('.ws_field_value').first();
|
862 |
+
|
863 |
+
var hasADefaultValue = itemTemplates.hasDefaultValue(menuItem.template_id, fieldName);
|
864 |
+
var defaultValue = itemTemplates.getDefaultValue(menuItem.template_id, fieldName);
|
865 |
+
var isDefault = hasADefaultValue && (menuItem[fieldName] === null);
|
866 |
+
|
867 |
+
if (fieldName == 'access_level') {
|
868 |
+
isDefault = (getFieldValue(menuItem, 'extra_capability', '') === '') && isEmptyObject(menuItem.grant_access);
|
869 |
+
}
|
870 |
+
|
871 |
+
field.toggleClass('ws_has_no_default', !hasADefaultValue);
|
872 |
+
field.toggleClass('ws_input_default', isDefault);
|
873 |
+
|
874 |
+
var displayValue = isDefault ? defaultValue : menuItem[fieldName];
|
875 |
+
if (knownMenuFields[fieldName].display !== null) {
|
876 |
+
displayValue = knownMenuFields[fieldName].display(menuItem, displayValue, input, containerNode);
|
877 |
+
}
|
878 |
+
|
879 |
+
if (fieldName == 'access_level') {
|
880 |
+
//Permissions display is a little complicated and could use improvement.
|
881 |
+
var requiredCap = getFieldValue(menuItem, 'access_level', '');
|
882 |
+
var extraCap = getFieldValue(menuItem, 'extra_capability', '');
|
883 |
+
|
884 |
+
displayValue = (menuItem.template_id === '') ? '< Custom >' : requiredCap;
|
885 |
+
if (extraCap !== '') {
|
886 |
+
if (menuItem.template_id === '') {
|
887 |
+
displayValue = extraCap;
|
888 |
+
} else {
|
889 |
+
displayValue = displayValue + '+' + extraCap;
|
890 |
+
}
|
891 |
+
}
|
892 |
+
}
|
893 |
+
|
894 |
+
setInputValue(input, displayValue);
|
895 |
+
});
|
896 |
+
}
|
897 |
+
|
898 |
+
function isEmptyObject(obj) {
|
899 |
+
for (var prop in obj) {
|
900 |
+
if (obj.hasOwnProperty(prop)) {
|
901 |
+
return false;
|
902 |
+
}
|
903 |
+
}
|
904 |
+
return true;
|
905 |
}
|
906 |
|
907 |
/*
|
908 |
+
* Get the current value of a single menu field.
|
909 |
*
|
910 |
+
* If the specified field is not set, this function will attempt to retrieve it
|
911 |
* from the "defaults" property of the menu object. If *that* fails, it will return
|
912 |
+
* the value of the optional third argument defaultValue.
|
913 |
*/
|
914 |
function getFieldValue(entry, fieldName, defaultValue){
|
915 |
if ( (typeof entry[fieldName] === 'undefined') || (entry[fieldName] === null) ) {
|
940 |
Parsing & encoding menu inputs
|
941 |
***************************************************************************/
|
942 |
|
943 |
+
/**
|
944 |
* Encode the current menu structure as JSON
|
945 |
*
|
946 |
+
* @return {String} A JSON-encoded string representing the current menu tree loaded in the editor.
|
|
|
947 |
*/
|
948 |
+
function encodeMenuAsJSON(tree){
|
949 |
+
if (typeof tree == 'undefined' || !tree) {
|
950 |
+
tree = readMenuTreeState();
|
951 |
+
}
|
952 |
+
tree.format = {
|
953 |
+
name: wsEditorData.menuFormatName,
|
954 |
+
version: wsEditorData.menuFormatVersion
|
955 |
+
};
|
956 |
return $.toJSON(tree);
|
957 |
}
|
958 |
|
959 |
function readMenuTreeState(){
|
960 |
var tree = {};
|
961 |
var menu_position = 0;
|
962 |
+
|
963 |
//Gather all menus and their items
|
964 |
+
$('#ws_menu_box').find('.ws_menu').each(function() {
|
965 |
+
var menu = readItemState(this, menu_position++);
|
966 |
+
|
967 |
//Attach the current menu to the main struct
|
968 |
var filename = (menu.file !== null)?menu.file:menu.defaults.file;
|
969 |
tree[filename] = menu;
|
970 |
});
|
|
|
|
|
|
|
971 |
|
972 |
+
return {
|
973 |
+
tree: tree
|
974 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
975 |
}
|
976 |
|
977 |
+
/**
|
978 |
* Extract the current menu item settings from its editor widget.
|
979 |
*
|
980 |
+
* @param itemDiv DOM node containing the editor widget, usually with the .ws_item or .ws_menu class.
|
981 |
+
* @param {Number} [position] Menu item position among its sibling menu items. Defaults to zero.
|
982 |
+
* @return {Object} A menu object in the tree format.
|
|
|
983 |
*/
|
984 |
+
function readItemState(itemDiv, position){
|
985 |
+
position = (typeof position == 'undefined') ? 0 : position;
|
986 |
+
|
987 |
+
itemDiv = $(itemDiv);
|
988 |
+
var item = $.extend({}, wsEditorData.blankMenuItem, itemDiv.data('menu_item'), readAllFields(itemDiv));
|
989 |
+
|
990 |
+
item.defaults = itemDiv.data('menu_item').defaults;
|
991 |
+
|
992 |
//Save the position data
|
|
|
|
|
|
|
993 |
item.position = position;
|
994 |
+
item.defaults.position = position; //The real default value will later overwrite this
|
995 |
+
|
996 |
+
item.separator = itemDiv.hasClass('ws_menu_separator');
|
997 |
+
item.hidden = menuHasFlag(itemDiv, 'hidden');
|
998 |
+
item.custom = menuHasFlag(itemDiv, 'custom');
|
999 |
+
|
1000 |
+
//Gather the menu's sub-items, if any
|
1001 |
+
item.items = [];
|
1002 |
+
var subMenuId = itemDiv.data('submenu_id');
|
1003 |
+
if (subMenuId) {
|
1004 |
+
var itemPosition = 0;
|
1005 |
+
$('#' + subMenuId).find('.ws_item').each(function () {
|
1006 |
+
var sub_item = readItemState(this, itemPosition++);
|
1007 |
+
item.items.push(sub_item);
|
1008 |
+
});
|
1009 |
+
}
|
1010 |
+
|
1011 |
return item;
|
1012 |
}
|
1013 |
|
1015 |
* Extract the values of all menu/item fields present in a container node
|
1016 |
*
|
1017 |
* Inputs:
|
1018 |
+
* container - a jQuery collection representing the node to read.
|
1019 |
*/
|
1020 |
function readAllFields(container){
|
1021 |
if ( !container.hasClass('ws_container') ){
|
1022 |
+
container = container.closest('.ws_container');
|
1023 |
}
|
1024 |
+
|
1025 |
if ( !container.data('field_editors_created') ){
|
1026 |
+
return container.data('menu_item');
|
1027 |
}
|
1028 |
+
|
1029 |
var state = {};
|
1030 |
+
|
1031 |
//Iterate over all fields of the item
|
1032 |
container.find('.ws_edit_field').each(function() {
|
1033 |
var field = $(this);
|
1034 |
+
|
1035 |
//Get the name of this field
|
1036 |
+
var field_name = field.data('field_name');
|
1037 |
//Skip if unnamed
|
1038 |
+
if (!field_name) {
|
1039 |
+
return true;
|
1040 |
+
}
|
1041 |
+
|
1042 |
//Find the field (usually an input or select element).
|
1043 |
+
var input_box = field.find('.ws_field_value');
|
1044 |
+
|
1045 |
//Save null if default used, custom value otherwise
|
1046 |
if (field.hasClass('ws_input_default')){
|
1047 |
state[field_name] = null;
|
1048 |
} else {
|
1049 |
+
state[field_name] = getInputValue(input_box);
|
|
|
|
|
|
|
|
|
1050 |
}
|
1051 |
+
return true;
|
1052 |
});
|
1053 |
+
|
1054 |
+
//Permission settings are not stored in the visible access_level field (that's just for show),
|
1055 |
+
//so do not attempt to read them from there.
|
1056 |
+
state['access_level'] = null;
|
1057 |
+
|
1058 |
return state;
|
1059 |
}
|
1060 |
|
1061 |
|
1062 |
/***************************************************************************
|
1063 |
+
Flag manipulation
|
1064 |
***************************************************************************/
|
1065 |
|
1066 |
var item_flags = {
|
1067 |
+
'custom':'This is a custom menu item',
|
1068 |
+
'unused':'This item was automatically (re)inserted into your custom menu because it is present in the default WordPress menu',
|
1069 |
+
'hidden':'This item is hidden'
|
1070 |
+
};
|
1071 |
+
|
1072 |
+
function setMenuFlag(item, flag, state) {
|
|
|
1073 |
item = $(item);
|
1074 |
+
|
1075 |
var item_class = 'ws_' + flag;
|
1076 |
var img_class = 'ws_' + flag + '_flag';
|
1077 |
+
|
1078 |
+
item.toggleClass(item_class, state);
|
1079 |
+
if (state) {
|
1080 |
+
//Add the flag image,
|
1081 |
+
var flag_container = item.find('.ws_flag_container');
|
1082 |
+
if ( flag_container.find('.' + img_class).length == 0 ){
|
1083 |
+
flag_container.append('<div class="ws_flag '+img_class+'" title="'+item_flags[flag]+'"></div>');
|
1084 |
+
}
|
1085 |
+
} else {
|
1086 |
+
//Remove the flag image.
|
1087 |
+
item.find('.' + img_class).remove();
|
1088 |
}
|
1089 |
}
|
1090 |
|
1091 |
+
function menuHasFlag(item, flag){
|
1092 |
+
return $(item).hasClass('ws_'+flag);
|
|
|
|
|
|
|
|
|
|
|
1093 |
}
|
1094 |
|
1095 |
+
/***********************************************************
|
1096 |
+
Capability manipulation
|
1097 |
+
************************************************************/
|
1098 |
+
|
1099 |
+
function actorCanAccessMenu(menuItem, actor) {
|
1100 |
+
if (!$.isPlainObject(menuItem.grant_access)) {
|
1101 |
+
menuItem.grant_access = {};
|
1102 |
+
}
|
1103 |
+
|
1104 |
+
//By default, any actor that has the required cap has access to the menu.
|
1105 |
+
//Users can override this on a per-menu basis.
|
1106 |
+
var requiredCap = getFieldValue(menuItem, 'access_level', '< Error: access_level is missing! >');
|
1107 |
+
var actorHasAccess = false;
|
1108 |
+
if (menuItem.grant_access.hasOwnProperty(actor)) {
|
1109 |
+
actorHasAccess = menuItem.grant_access[actor];
|
1110 |
} else {
|
1111 |
+
actorHasAccess = AmeCapabilityManager.hasCap(actor, requiredCap, menuItem.grant_access);
|
1112 |
}
|
1113 |
+
return actorHasAccess;
|
1114 |
}
|
1115 |
|
1116 |
+
function setActorAccess(containerNode, actor, allowAccess) {
|
1117 |
+
var menuItem = containerNode.data('menu_item');
|
1118 |
+
|
1119 |
+
//grant_access comes from PHP, which JSON-encodes empty assoc. arrays as arrays.
|
1120 |
+
//However, we want it to be a dictionary.
|
1121 |
+
if (!$.isPlainObject(menuItem.grant_access)) {
|
1122 |
+
menuItem.grant_access = {};
|
1123 |
+
}
|
1124 |
+
|
1125 |
+
menuItem.grant_access[actor] = allowAccess;
|
1126 |
}
|
1127 |
|
1128 |
+
function setSelectedActor(actor) {
|
1129 |
+
//Check if the specified actor really exists. The actor ID
|
1130 |
+
//could be invalid if it was supplied by the user.
|
1131 |
+
if (actor !== null) {
|
1132 |
+
var newSelectedItem = $('a[href$="#'+ actor +'"]');
|
1133 |
+
if (newSelectedItem.length === 0) {
|
1134 |
+
return;
|
1135 |
+
}
|
1136 |
+
}
|
1137 |
+
|
1138 |
+
selectedActor = actor;
|
1139 |
+
|
1140 |
+
//Highlight the actor.
|
1141 |
+
var actorSelector = $('#ws_actor_selector');
|
1142 |
+
$('.current', actorSelector).removeClass('current');
|
1143 |
+
|
1144 |
+
if (selectedActor == null) {
|
1145 |
+
$('a.ws_no_actor').addClass('current');
|
1146 |
+
} else {
|
1147 |
+
newSelectedItem.addClass('current');
|
1148 |
+
}
|
1149 |
+
|
1150 |
+
//There are some UI elements that can be visible or hidden depending on whether an actor is selected.
|
1151 |
+
var editorNode = $('#ws_menu_editor');
|
1152 |
+
editorNode.toggleClass('ws_is_actor_view', (selectedActor != null));
|
1153 |
+
|
1154 |
+
//Update the menu item states to indicate whether they're accessible.
|
1155 |
+
if (selectedActor != null) {
|
1156 |
+
editorNode.find('.ws_container').each(function() {
|
1157 |
+
updateActorAccessUi($(this));
|
1158 |
+
});
|
1159 |
+
} else {
|
1160 |
+
editorNode.find('.ws_is_hidden_for_actor').removeClass('ws_is_hidden_for_actor');
|
1161 |
+
}
|
1162 |
+
}
|
1163 |
+
|
1164 |
+
/**
|
1165 |
+
* Make a menu item inaccessible to everyone except a particular actor.
|
1166 |
+
*
|
1167 |
+
* Will not change access settings for actors that are more specific than the input actor.
|
1168 |
+
* For example, if the input actor is a "role:", this function will only disable other roles,
|
1169 |
+
* but will leave "user:" actors untouched.
|
1170 |
+
*
|
1171 |
+
* @param {Object} menuItem
|
1172 |
+
* @param {String} actor
|
1173 |
+
* @return {Object}
|
1174 |
+
*/
|
1175 |
+
function denyAccessForAllExcept(menuItem, actor) {
|
1176 |
+
//grant_access comes from PHP, which JSON-encodes empty assoc. arrays as arrays.
|
1177 |
+
//However, we want it to be a dictionary.
|
1178 |
+
if (!$.isPlainObject(menuItem.grant_access)) {
|
1179 |
+
menuItem.grant_access = {};
|
1180 |
+
}
|
1181 |
+
|
1182 |
+
$.each(wsEditorData.actors, function(otherActor) {
|
1183 |
+
//If the input actor is more or equally specific...
|
1184 |
+
if (AmeCapabilityManager.compareActorSpecificity(actor, otherActor) >= 0) {
|
1185 |
+
menuItem.grant_access[otherActor] = false;
|
1186 |
+
}
|
1187 |
+
});
|
1188 |
+
menuItem.grant_access[actor] = true;
|
1189 |
+
return menuItem;
|
1190 |
}
|
1191 |
|
1192 |
+
/***************************************************************************
|
1193 |
+
Event handlers
|
1194 |
+
***************************************************************************/
|
1195 |
+
|
1196 |
//Cut & paste stuff
|
1197 |
var menu_in_clipboard = null;
|
|
|
|
|
1198 |
var ws_paste_count = 0;
|
1199 |
|
1200 |
$(document).ready(function(){
|
1201 |
+
//Some editor elements are only available in the Pro version.
|
1202 |
+
if (wsEditorData.wsMenuEditorPro) {
|
1203 |
knownMenuFields['open_in'].visible = true;
|
1204 |
+
knownMenuFields['access_level'].visible = true;
|
1205 |
+
knownMenuFields['extra_capability'].visible = false; //Superseded by the "access_level" field.
|
1206 |
+
$('.ws_hide_if_pro').hide();
|
1207 |
}
|
1208 |
+
|
1209 |
//Make the top menu box sortable (we only need to do this once)
|
1210 |
var mainMenuBox = $('#ws_menu_box');
|
1211 |
makeBoxSortable(mainMenuBox);
|
1212 |
+
|
1213 |
/***************************************************************************
|
1214 |
Event handlers for editor widgets
|
1215 |
***************************************************************************/
|
1216 |
var menuEditorNode = $('#ws_menu_editor');
|
1217 |
+
|
1218 |
//Highlight the clicked menu item and show it's submenu
|
1219 |
var currentVisibleSubmenu = null;
|
1220 |
+
menuEditorNode.on('click', '.ws_container', (function () {
|
1221 |
var container = $(this);
|
1222 |
if ( container.hasClass('ws_active') ){
|
1223 |
return;
|
1224 |
}
|
1225 |
+
|
1226 |
//Highlight the active item and un-highlight the previous one
|
1227 |
+
container.addClass('ws_active');
|
1228 |
container.siblings('.ws_active').removeClass('ws_active');
|
1229 |
if ( container.hasClass('ws_menu') ){
|
1230 |
//Show/hide the appropriate submenu
|
1234 |
currentVisibleSubmenu = $('#'+container.data('submenu_id')).show();
|
1235 |
}
|
1236 |
}));
|
1237 |
+
|
1238 |
//Show/hide a menu's properties
|
1239 |
+
menuEditorNode.on('click', '.ws_edit_link', (function () {
|
1240 |
+
var container = $(this).parents('.ws_container').first();
|
1241 |
var box = container.find('.ws_editbox');
|
1242 |
+
|
1243 |
+
//For performance, the property editors for each menu are only created
|
1244 |
//when the user tries to access access them for the first time.
|
1245 |
if ( !container.data('field_editors_created') ){
|
1246 |
+
buildEditboxFields(box, container.data('menu_item'), container.hasClass('ws_menu'));
|
1247 |
container.data('field_editors_created', true);
|
1248 |
+
updateItemEditor(container);
|
1249 |
}
|
1250 |
+
|
1251 |
$(this).toggleClass('ws_edit_link_expanded');
|
1252 |
//show/hide the editbox
|
1253 |
if ($(this).hasClass('ws_edit_link_expanded')){
|
1258 |
box.hide();
|
1259 |
}
|
1260 |
}));
|
1261 |
+
|
1262 |
//The "Default" button : Reset to default value when clicked
|
1263 |
+
menuEditorNode.on('click', '.ws_reset_button', (function () {
|
1264 |
+
//Find the field div (it holds the field name)
|
1265 |
+
var field = $(this).parents('.ws_edit_field');
|
1266 |
+
var fieldName = field.data('field_name');
|
1267 |
//Find the related input field
|
1268 |
var input = field.find('.ws_field_value');
|
1269 |
+
|
1270 |
+
if ( (input.length > 0) && (field.length > 0) && fieldName ) {
|
1271 |
+
//Extract the default value from the menu item.
|
1272 |
+
var containerNode = field.closest('.ws_container');
|
1273 |
+
var menuItem = containerNode.data('menu_item');
|
1274 |
+
|
1275 |
+
if (fieldName == 'access_level') {
|
1276 |
+
//This is a pretty nasty hack.
|
1277 |
+
menuItem.grant_access = {};
|
1278 |
+
menuItem.extra_capability = null;
|
1279 |
}
|
1280 |
+
|
1281 |
+
if (itemTemplates.hasDefaultValue(menuItem.template_id, fieldName)) {
|
1282 |
+
menuItem[fieldName] = null;
|
1283 |
+
updateItemEditor(containerNode);
|
1284 |
+
updateParentAccessUi(containerNode);
|
1285 |
+
}
|
1286 |
+
}
|
1287 |
}));
|
1288 |
|
1289 |
//When a field is edited, change it's appearance if it's contents don't match the default value.
|
1290 |
function fieldValueChange(){
|
1291 |
var input = $(this);
|
1292 |
var field = input.parents('.ws_edit_field').first();
|
1293 |
+
var fieldName = field.data('field_name');
|
1294 |
+
|
1295 |
+
if (fieldName == 'access_level') {
|
1296 |
+
//This field is read-only and can never be directly edited by the user.
|
1297 |
+
//Ignore spurious change events.
|
1298 |
+
return;
|
1299 |
}
|
1300 |
+
|
1301 |
+
var containerNode = field.parents('.ws_container').first();
|
1302 |
+
var menuItem = containerNode.data('menu_item');
|
1303 |
+
|
1304 |
+
var oldValue = menuItem[fieldName];
|
1305 |
+
var value = getInputValue(input);
|
1306 |
+
var defaultValue = itemTemplates.getDefaultValue(menuItem.template_id, fieldName);
|
1307 |
+
var hasADefaultValue = (defaultValue !== null);
|
1308 |
+
|
1309 |
+
//Some fields/templates have no default values.
|
1310 |
+
field.toggleClass('ws_has_no_default', !hasADefaultValue);
|
1311 |
+
if (!hasADefaultValue) {
|
1312 |
+
field.removeClass('ws_input_default');
|
1313 |
+
}
|
1314 |
+
|
1315 |
+
if (field.hasClass('ws_input_default') && (value == defaultValue)) {
|
1316 |
+
value = null; //null = use default.
|
1317 |
+
}
|
1318 |
+
|
1319 |
+
//Ignore changes where the new value is the same as the old one.
|
1320 |
+
if (value === oldValue) {
|
1321 |
+
return;
|
1322 |
+
}
|
1323 |
+
|
1324 |
+
//Update the item.
|
1325 |
+
if (knownMenuFields[fieldName].write !== null) {
|
1326 |
+
knownMenuFields[fieldName].write(menuItem, value, input, containerNode);
|
1327 |
+
} else {
|
1328 |
+
menuItem[fieldName] = value;
|
1329 |
+
}
|
1330 |
+
|
1331 |
+
updateItemEditor(containerNode);
|
1332 |
+
updateParentAccessUi(containerNode)
|
1333 |
}
|
1334 |
menuEditorNode.on('click change', '.ws_field_value', fieldValueChange);
|
1335 |
|
1336 |
//Show/hide advanced fields
|
1337 |
+
menuEditorNode.on('click', '.ws_toggle_advanced_fields', function(){
|
1338 |
var self = $(this);
|
1339 |
var advancedFields = self.parents('.ws_container').first().find('.ws_advanced');
|
1340 |
+
|
1341 |
if ( advancedFields.is(':visible') ){
|
1342 |
advancedFields.hide();
|
1343 |
+
self.text(wsEditorData.captionShowAdvanced);
|
1344 |
} else {
|
1345 |
advancedFields.show();
|
1346 |
+
self.text(wsEditorData.captionHideAdvanced);
|
1347 |
}
|
1348 |
+
|
1349 |
return false;
|
1350 |
});
|
1351 |
+
|
1352 |
+
//Allow/forbid items in actor-specific views
|
1353 |
+
menuEditorNode.on('click', 'input.ws_actor_access_checkbox', function() {
|
1354 |
+
if (selectedActor == null) {
|
1355 |
+
return;
|
1356 |
+
}
|
1357 |
+
|
1358 |
+
var checked = $(this).is(':checked');
|
1359 |
+
var containerNode = $(this).closest('.ws_container');
|
1360 |
+
|
1361 |
+
setActorAccessForTreeAndUpdateUi(containerNode, selectedActor, checked);
|
1362 |
+
});
|
1363 |
+
|
1364 |
+
/**
|
1365 |
+
* This confusingly named function sets actor access for the specified menu item
|
1366 |
+
* and all of its children (if any). It also updates the UI with the new settings.
|
1367 |
+
*
|
1368 |
+
* (And it violates SRP in a particularly egregious manner.)
|
1369 |
+
*
|
1370 |
+
* @param containerNode
|
1371 |
+
* @param {String} actor
|
1372 |
+
* @param {Boolean} allowAccess
|
1373 |
+
*/
|
1374 |
+
function setActorAccessForTreeAndUpdateUi(containerNode, actor, allowAccess) {
|
1375 |
+
setActorAccess(containerNode, actor, allowAccess);
|
1376 |
+
|
1377 |
+
//Apply the same permissions to sub-menus.
|
1378 |
+
var subMenuId = containerNode.data('submenu_id');
|
1379 |
+
if (subMenuId && containerNode.hasClass('ws_menu')) {
|
1380 |
+
$('.ws_item', '#' + subMenuId).each(function() {
|
1381 |
+
var node = $(this);
|
1382 |
+
setActorAccess(node, actor, allowAccess);
|
1383 |
+
updateItemEditor(node);
|
1384 |
+
});
|
1385 |
+
}
|
1386 |
+
|
1387 |
+
updateItemEditor(containerNode);
|
1388 |
+
updateParentAccessUi(containerNode);
|
1389 |
+
}
|
1390 |
+
|
1391 |
+
/*************************************************************************
|
1392 |
+
Access editor dialog
|
1393 |
+
*************************************************************************/
|
1394 |
+
|
1395 |
+
var accessEditorState = {
|
1396 |
+
containerNode : null,
|
1397 |
+
menuItem: null,
|
1398 |
+
rowPrefix: 'access_settings_for-'
|
1399 |
+
};
|
1400 |
+
|
1401 |
+
$('#ws_menu_access_editor').dialog({
|
1402 |
+
autoOpen: false,
|
1403 |
+
closeText: ' ',
|
1404 |
+
modal: true,
|
1405 |
+
minHeight: 100,
|
1406 |
+
draggable: false
|
1407 |
+
});
|
1408 |
+
|
1409 |
+
menuEditorNode.on('click', '.ws_launch_access_editor', function() {
|
1410 |
+
var containerNode = $(this).parents('.ws_container').first();
|
1411 |
+
var menuItem = containerNode.data('menu_item');
|
1412 |
+
|
1413 |
+
//Write the values of this item to the editor fields.
|
1414 |
+
var editor = $('#ws_menu_access_editor');
|
1415 |
+
|
1416 |
+
var requiredCap = getFieldValue(menuItem, 'access_level', '< Error: access_level is missing! >');
|
1417 |
+
var requiredCapField = editor.find('#ws_required_capability').empty();
|
1418 |
+
if (menuItem.template_id === '') {
|
1419 |
+
//Custom items have no required caps, only what users set.
|
1420 |
+
requiredCapField.empty().append('<em>None</em>');
|
1421 |
+
} else {
|
1422 |
+
requiredCapField.text(requiredCap);
|
1423 |
+
}
|
1424 |
+
|
1425 |
+
editor.find('#ws_extra_capability').val(getFieldValue(menuItem, 'extra_capability', ''));
|
1426 |
+
|
1427 |
+
//Generate the actor list.
|
1428 |
+
var table = editor.find('.ws_role_table_body tbody').empty();
|
1429 |
+
var alternate = '';
|
1430 |
+
for(var actor in wsEditorData.actors) {
|
1431 |
+
if (!wsEditorData.actors.hasOwnProperty(actor)) {
|
1432 |
+
continue;
|
1433 |
+
}
|
1434 |
+
var actorName = wsEditorData.actors[actor];
|
1435 |
+
|
1436 |
+
var checkboxId = 'allow_' + actor.replace(/[^a-zA-Z0-9_]/g, '_');
|
1437 |
+
var checkbox = $('<input type="checkbox">').addClass('ws_role_access').attr('id', checkboxId);
|
1438 |
+
|
1439 |
+
var actorHasAccess = actorCanAccessMenu(menuItem, actor);
|
1440 |
+
if (actorHasAccess) {
|
1441 |
+
checkbox.attr('checked', 'checked');
|
1442 |
+
}
|
1443 |
+
|
1444 |
+
alternate = (alternate == '') ? 'alternate' : '';
|
1445 |
+
|
1446 |
+
var cell = '<td>';
|
1447 |
+
var row = $('<tr>').data('actor', actor).attr('class', alternate).append(
|
1448 |
+
$(cell).addClass('ws_column_role post-title').append(
|
1449 |
+
$('<label>').attr('for', checkboxId).append(
|
1450 |
+
$('<strong>').text(actorName)
|
1451 |
+
)
|
1452 |
+
),
|
1453 |
+
$(cell).addClass('ws_column_access').append(checkbox)
|
1454 |
+
);
|
1455 |
+
|
1456 |
+
table.append(row);
|
1457 |
+
}
|
1458 |
+
|
1459 |
+
accessEditorState.containerNode = containerNode;
|
1460 |
+
accessEditorState.menuItem = menuItem;
|
1461 |
+
|
1462 |
+
//Show/hide the hint about sub menus overriding menu permissions.
|
1463 |
+
var itemHasSubmenus = containerNode.data('submenu_id') &&
|
1464 |
+
$('#' + containerNode.data('submenu_id')).find('.ws_item').length > 0;
|
1465 |
+
var hintIsEnabled = !wsEditorData.showHints.hasOwnProperty('ws_hint_menu_permissions') || wsEditorData.showHints['ws_hint_menu_permissions'];
|
1466 |
+
$('#ws_hint_menu_permissions').toggle(hintIsEnabled && itemHasSubmenus);
|
1467 |
+
|
1468 |
+
//Warn the user if the required capability == role. Can't make it less restrictive.
|
1469 |
+
var roleError = $('#ws_hardcoded_role_error');
|
1470 |
+
if (requiredCap && AmeCapabilityManager.roleExists(requiredCap)) {
|
1471 |
+
roleError.show();
|
1472 |
+
$('#ws_hardcoded_role_name').text(requiredCap);
|
1473 |
+
} else {
|
1474 |
+
roleError.hide();
|
1475 |
+
}
|
1476 |
+
|
1477 |
+
editor.dialog('open');
|
1478 |
+
});
|
1479 |
+
|
1480 |
+
$('#ws_save_access_settings').click(function() {
|
1481 |
+
//Save the new settings.
|
1482 |
+
accessEditorState.menuItem.extra_capability = $('#ws_extra_capability').val();
|
1483 |
+
|
1484 |
+
var grantAccess = accessEditorState.menuItem.grant_access;
|
1485 |
+
if (!$.isPlainObject(grantAccess)) {
|
1486 |
+
grantAccess = {};
|
1487 |
+
}
|
1488 |
+
var editor = $('#ws_menu_access_editor');
|
1489 |
+
editor.find('.ws_role_table_body tbody tr').each(function() {
|
1490 |
+
var row = $(this);
|
1491 |
+
var actor = row.data('actor');
|
1492 |
+
grantAccess[actor] = row.find('input.ws_role_access').is(':checked');
|
1493 |
+
});
|
1494 |
+
accessEditorState.menuItem.grant_access = grantAccess;
|
1495 |
+
|
1496 |
+
updateItemEditor(accessEditorState.containerNode);
|
1497 |
+
editor.dialog('close');
|
1498 |
+
});
|
1499 |
+
|
1500 |
+
/***************************************************************************
|
1501 |
+
General dialog handlers
|
1502 |
+
***************************************************************************/
|
1503 |
+
|
1504 |
+
$(document).on('click', '.ws_close_dialog', function() {
|
1505 |
+
$(this).parents('.ui-dialog-content').dialog('close');
|
1506 |
+
});
|
1507 |
+
|
1508 |
+
|
1509 |
/***************************************************************************
|
1510 |
+
Drop-down list for combo-box fields
|
1511 |
***************************************************************************/
|
1512 |
|
1513 |
+
var capSelectorDropdown = $('#ws_cap_selector');
|
1514 |
+
var currentDropdownOwner = null; //The input element that the dropdown is currently associated with.
|
1515 |
+
var isDropdownBeingHidden = false;
|
1516 |
+
|
1517 |
+
//Show/hide the capability drop-down list when the trigger button is clicked
|
1518 |
+
$('#ws_trigger_capability_dropdown').on('mousedown click', onDropdownTriggerClicked);
|
1519 |
+
menuEditorNode.on('mousedown click', '.ws_dropdown_button', onDropdownTriggerClicked);
|
1520 |
+
|
1521 |
+
function onDropdownTriggerClicked(event){
|
1522 |
+
var inputBox = null;
|
|
|
|
|
|
|
|
|
|
|
1523 |
var button = $(this);
|
1524 |
+
|
1525 |
+
//Find the input associated with the button that was clicked.
|
1526 |
+
if ( button.attr('id') == 'ws_trigger_capability_dropdown' ) {
|
1527 |
+
inputBox = $('#ws_extra_capability');
|
1528 |
+
} else {
|
1529 |
+
inputBox = button.closest('.ws_edit_field').find('.ws_field_value').first();
|
1530 |
+
}
|
1531 |
+
|
1532 |
+
//If the user clicks the same button again while the dropdown is already visible,
|
1533 |
+
//ignore the click. The dropdown will be hidden by its "blur" handler.
|
1534 |
+
if (event.type == 'mousedown') {
|
1535 |
+
if ( capSelectorDropdown.is(':visible') && inputBox.is(currentDropdownOwner) ) {
|
1536 |
+
isDropdownBeingHidden = true;
|
1537 |
+
}
|
1538 |
+
return;
|
1539 |
+
} else if (isDropdownBeingHidden) {
|
1540 |
+
isDropdownBeingHidden = false; //Ignore the click event.
|
1541 |
return;
|
1542 |
}
|
|
|
1543 |
|
1544 |
+
//A jQuery UI dialog widget will prevent focus from leaving the dialog. So if we want
|
1545 |
+
//the dropdown to be properly focused when displaying it in a dialog, we must make it
|
1546 |
+
//a child of the dialog's DOM node (and vice versa when it's not in a dialog).
|
1547 |
+
var parentContainer = $(this).closest('.ui-dialog, #ws_menu_editor');
|
1548 |
+
if ((parentContainer.length > 0) && (capSelectorDropdown.closest(parentContainer).length == 0)) {
|
1549 |
+
var oldHeight = capSelectorDropdown.height(); //Height seems to reset when moving to a new parent.
|
1550 |
+
capSelectorDropdown.detach().appendTo(parentContainer).height(oldHeight);
|
1551 |
+
}
|
1552 |
|
1553 |
+
//Pre-select the current capability (will clear selection if there's no match).
|
1554 |
+
capSelectorDropdown.val(inputBox.val()).show();
|
1555 |
|
1556 |
+
//Move the drop-down near the input box.
|
1557 |
var inputPos = inputBox.offset();
|
1558 |
+
capSelectorDropdown
|
1559 |
+
.css({
|
1560 |
+
position: 'absolute',
|
1561 |
+
zIndex: 1010 //Must be higher than the permissions dialog overlay.
|
1562 |
+
})
|
1563 |
+
.offset({
|
1564 |
+
left: inputPos.left,
|
1565 |
+
top : inputPos.top + inputBox.outerHeight()
|
1566 |
+
}).
|
1567 |
+
width(inputBox.outerWidth());
|
1568 |
+
|
1569 |
+
currentDropdownOwner = inputBox;
|
1570 |
+
capSelectorDropdown.focus();
|
1571 |
+
}
|
1572 |
+
|
1573 |
+
//Also show it when the user presses the down arrow in the input field (doesn't work in Opera).
|
1574 |
+
$('#ws_extra_capability').bind('keyup', function(event){
|
1575 |
if ( event.which == 40 ){
|
1576 |
+
$('#ws_trigger_capability_dropdown').click();
|
1577 |
}
|
1578 |
});
|
1579 |
+
|
1580 |
+
//Event handlers for the drop-down lists themselves
|
1581 |
var dropdownNodes = $('.ws_dropdown');
|
1582 |
+
|
1583 |
+
// Hide capability drop-down when it loses focus.
|
1584 |
dropdownNodes.blur(function(event){
|
1585 |
+
console.log('Hiding dropdown because it lost focus.', event);
|
1586 |
+
capSelectorDropdown.hide();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1587 |
});
|
1588 |
+
|
1589 |
dropdownNodes.keydown(function(event){
|
1590 |
+
|
1591 |
+
//Hide it when the user presses Esc
|
|
|
1592 |
if ( event.which == 27 ){
|
1593 |
+
capSelectorDropdown.hide();
|
1594 |
+
if (currentDropdownOwner) {
|
1595 |
+
currentDropdownOwner.focus();
|
|
|
|
|
1596 |
}
|
1597 |
+
|
|
|
1598 |
//Select an item & hide the list when the user presses Enter or Tab
|
1599 |
} else if ( (event.which == 13) || (event.which == 9) ){
|
1600 |
+
capSelectorDropdown.hide();
|
1601 |
+
|
1602 |
+
if (currentDropdownOwner) {
|
1603 |
+
if ( capSelectorDropdown.val() ){
|
1604 |
+
currentDropdownOwner.val(capSelectorDropdown.val()).change();
|
1605 |
+
}
|
1606 |
+
currentDropdownOwner.focus();
|
1607 |
}
|
1608 |
+
|
|
|
|
|
|
|
1609 |
event.preventDefault();
|
1610 |
}
|
1611 |
});
|
1612 |
+
|
1613 |
+
//Eat Tab keys to prevent focus theft. Required to make the "select item on Tab" thing work.
|
1614 |
dropdownNodes.keyup(function(event){
|
1615 |
if ( event.which == 9 ){
|
1616 |
event.preventDefault();
|
1617 |
}
|
1618 |
+
});
|
1619 |
+
|
1620 |
+
|
1621 |
//Update the input & hide the list when an option is clicked
|
1622 |
dropdownNodes.click(function(){
|
1623 |
+
if (capSelectorDropdown.val()){
|
1624 |
+
capSelectorDropdown.hide();
|
1625 |
+
if (currentDropdownOwner) {
|
1626 |
+
currentDropdownOwner.val(capSelectorDropdown.val()).change().focus();
|
1627 |
+
}
|
1628 |
}
|
|
|
|
|
|
|
|
|
|
|
1629 |
});
|
1630 |
+
|
1631 |
//Highlight an option when the user mouses over it (doesn't work in IE)
|
1632 |
dropdownNodes.mousemove(function(event){
|
1633 |
if ( !event.target ){
|
1634 |
return;
|
1635 |
}
|
1636 |
+
|
1637 |
+
var option = event.target;
|
1638 |
+
if ( (typeof option['selected'] !== 'undefined') && !option.selected && option.value ){
|
1639 |
+
option.selected = true;
|
1640 |
}
|
1641 |
});
|
1642 |
|
1654 |
//Assign the selected icon to the menu.
|
1655 |
if (currentIconButton) {
|
1656 |
var container = currentIconButton.closest('.ws_container');
|
1657 |
+
var item = container.data('menu_item');
|
|
|
1658 |
|
1659 |
//Remove the existing icon class, if any.
|
1660 |
+
var cssClass = getFieldValue(item, 'css_class', '');
|
1661 |
cssClass = jsTrim( cssClass.replace(/\bmenu-icon-[^\s]+\b/, '') );
|
1662 |
|
1663 |
if (selectedIcon.data('icon-class')) {
|
1664 |
//Add the new class.
|
1665 |
cssClass = selectedIcon.data('icon-class') + ' ' + cssClass;
|
1666 |
//Can't have both a class and an image or we'll get two overlapping icons.
|
1667 |
+
item.icon_url = '';
|
1668 |
} else if (selectedIcon.data('icon-url')) {
|
1669 |
+
item.icon_url = selectedIcon.data('icon-url');
|
1670 |
}
|
1671 |
+
item.css_class = cssClass;
|
1672 |
+
|
1673 |
+
updateItemEditor(container);
|
1674 |
}
|
1675 |
|
1676 |
currentIconButton = null;
|
1689 |
|
1690 |
currentIconButton = button;
|
1691 |
|
1692 |
+
var menuItem = currentIconButton.closest('.ws_container').data('menu_item');
|
1693 |
+
var cssClass = getFieldValue(menuItem, 'css_class', '');
|
1694 |
+
var iconUrl = getFieldValue(menuItem, 'icon_url', '');
|
|
|
|
|
1695 |
|
1696 |
var customImageOption = iconSelector.find('.ws_custom_image_icon').hide();
|
1697 |
|
1764 |
//Set the menu icon to the attachment URL.
|
1765 |
if (currentIconButton) {
|
1766 |
var container = currentIconButton.closest('.ws_container');
|
1767 |
+
var item = container.data('menu_item');
|
|
|
|
|
|
|
1768 |
|
1769 |
//Remove the existing icon class, if any.
|
1770 |
+
var cssClass = getFieldValue(item, 'css_class', '');
|
1771 |
+
item.css_class = jsTrim( cssClass.replace(/\bmenu-icon-[^\s]+\b/, '') );
|
1772 |
|
1773 |
//Set the new icon URL.
|
1774 |
+
item.icon_url = attachment.attributes.url;
|
1775 |
|
1776 |
+
updateItemEditor(container);
|
|
|
1777 |
}
|
1778 |
|
1779 |
currentIconButton = null;
|
1804 |
currentIconButton = null;
|
1805 |
}
|
1806 |
});
|
1807 |
+
|
1808 |
+
|
1809 |
/*************************************************************************
|
1810 |
Menu toolbar buttons
|
1811 |
*************************************************************************/
|
1812 |
+
function getSelectedMenu() {
|
1813 |
+
return $('#ws_menu_box').find('.ws_active');
|
1814 |
+
}
|
1815 |
+
|
1816 |
//Show/Hide menu
|
1817 |
$('#ws_hide_menu').click(function () {
|
1818 |
//Get the selected menu
|
1819 |
+
var selection = getSelectedMenu();
|
1820 |
if (!selection.length) return;
|
1821 |
+
|
1822 |
//Mark the menu as hidden/visible
|
1823 |
+
var menuItem = selection.data('menu_item');
|
1824 |
+
menuItem.hidden = !menuItem.hidden;
|
1825 |
+
setMenuFlag(selection, 'hidden', menuItem.hidden);
|
1826 |
+
|
1827 |
//Also mark all of it's submenus as hidden/visible
|
1828 |
+
$('#' + selection.data('submenu_id') + ' .ws_item').each(function(){
|
1829 |
+
var submenuItem = $(this).data('menu_item');
|
1830 |
+
submenuItem.hidden = menuItem.hidden;
|
1831 |
+
setMenuFlag(this, 'hidden', submenuItem.hidden);
|
1832 |
+
});
|
|
|
|
|
|
|
|
|
1833 |
});
|
1834 |
+
|
1835 |
//Delete menu
|
1836 |
$('#ws_delete_menu').click(function () {
|
1837 |
//Get the selected menu
|
1838 |
+
var selection = getSelectedMenu();
|
1839 |
if (!selection.length) return;
|
1840 |
+
|
1841 |
if (confirm('Delete this menu?')){
|
1842 |
//Delete the submenu first
|
1843 |
$('#' + selection.data('submenu_id')).remove();
|
1845 |
selection.remove();
|
1846 |
}
|
1847 |
});
|
1848 |
+
|
1849 |
//Copy menu
|
1850 |
$('#ws_copy_menu').click(function () {
|
1851 |
//Get the selected menu
|
1852 |
+
var selection = $('#ws_menu_box').find('.ws_active');
|
1853 |
if (!selection.length) return;
|
1854 |
+
|
1855 |
//Store a copy of the current menu state in clipboard
|
1856 |
+
menu_in_clipboard = readItemState(selection);
|
1857 |
});
|
1858 |
+
|
1859 |
//Cut menu
|
1860 |
$('#ws_cut_menu').click(function () {
|
1861 |
//Get the selected menu
|
1862 |
+
var selection = $('#ws_menu_box').find('.ws_active');
|
1863 |
if (!selection.length) return;
|
1864 |
+
|
1865 |
//Store a copy of the current menu state in clipboard
|
1866 |
+
menu_in_clipboard = readItemState(selection);
|
1867 |
+
|
1868 |
+
//Remove the original menu and submenu
|
|
|
1869 |
$('#'+selection.data('submenu_id')).remove();
|
1870 |
+
selection.remove();
|
1871 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1872 |
|
1873 |
+
//Paste menu
|
1874 |
+
function pasteMenu(menu, afterMenu) {
|
1875 |
//The user shouldn't need to worry about giving separators a unique filename.
|
1876 |
if (menu.separator) {
|
1877 |
+
menu.defaults.file = randomMenuId('separator_');
|
1878 |
}
|
1879 |
|
1880 |
+
//If we're pasting from a sub-menu, we may need to fix some properties
|
1881 |
+
//that are blank for sub-menu items but required for top-level menus.
|
1882 |
+
if (getFieldValue(menu, 'css_class', '') == '') {
|
1883 |
+
menu.css_class = 'menu-top';
|
1884 |
+
}
|
1885 |
+
if (getFieldValue(menu, 'icon_url', '') == '') {
|
1886 |
+
menu.icon_url = 'images/generic.png';
|
1887 |
+
}
|
1888 |
+
if (getFieldValue(menu, 'hookname', '') == '') {
|
1889 |
+
menu.hookname = randomMenuId();
|
1890 |
+
}
|
1891 |
+
|
1892 |
+
//Paste the menu after the specified one, or at the end of the list.
|
1893 |
+
if (afterMenu) {
|
1894 |
+
outputTopMenu(menu, afterMenu);
|
1895 |
} else {
|
|
|
1896 |
outputTopMenu(menu);
|
1897 |
}
|
1898 |
+
}
|
1899 |
+
|
1900 |
+
$('#ws_paste_menu').click(function () {
|
1901 |
+
//Check if anything has been copied/cut
|
1902 |
+
if (!menu_in_clipboard) return;
|
1903 |
+
|
1904 |
+
var menu = $.extend(true, {}, menu_in_clipboard);
|
1905 |
+
|
1906 |
+
//Get the selected menu
|
1907 |
+
var selection = $('#ws_menu_box').find('.ws_active');
|
1908 |
+
//Paste the menu after the selection.
|
1909 |
+
pasteMenu(menu, (selection.length > 0) ? selection : null);
|
1910 |
});
|
1911 |
+
|
1912 |
//New menu
|
1913 |
$('#ws_new_menu').click(function () {
|
1914 |
ws_paste_count++;
|
1915 |
+
|
1916 |
//The new menu starts out rather bare
|
1917 |
+
var randomId = randomMenuId();
|
1918 |
+
var menu = $.extend({}, wsEditorData.blankMenuItem, {
|
1919 |
+
custom: true, //Important : flag the new menu as custom, or it won't show up after saving.
|
1920 |
+
template_id : '',
|
1921 |
+
menu_title : 'Custom Menu ' + ws_paste_count,
|
1922 |
+
file : randomId,
|
1923 |
+
items: [],
|
1924 |
+
defaults: itemTemplates.getDefaults('')
|
1925 |
+
});
|
1926 |
+
|
1927 |
+
//Make it accessible only to the current actor if one is selected.
|
1928 |
+
if (selectedActor != null) {
|
1929 |
+
denyAccessForAllExcept(menu, selectedActor);
|
1930 |
+
}
|
1931 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1932 |
//Insert the new menu
|
1933 |
+
var selection = $('#ws_menu_box').find('.ws_active');
|
1934 |
+
var result = outputTopMenu(menu, (selection.length > 0) ? selection : null);
|
1935 |
+
|
1936 |
//The menus's editbox is always open
|
1937 |
result.menu.find('.ws_edit_link').click();
|
1938 |
});
|
1939 |
+
|
1940 |
//New separator
|
1941 |
+
$('#ws_new_separator, #ws_new_submenu_separator').click(function () {
|
1942 |
ws_paste_count++;
|
1943 |
+
|
1944 |
//The new menu starts out rather bare
|
1945 |
+
var randomId = randomMenuId('separator_');
|
1946 |
+
var menu = $.extend(true, {}, wsEditorData.blankMenuItem, {
|
1947 |
+
separator: true, //Flag as a separator
|
1948 |
+
custom: false, //Separators don't need to flagged as custom to be retained.
|
1949 |
+
items: [],
|
1950 |
+
defaults: {
|
1951 |
+
separator: true,
|
1952 |
+
css_class : 'wp-menu-separator',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1953 |
access_level : 'read',
|
1954 |
file : randomId,
|
1955 |
+
hookname : randomId
|
|
|
|
|
|
|
|
|
|
|
|
|
1956 |
}
|
1957 |
+
});
|
1958 |
+
|
1959 |
+
if ( $(this).attr('id').indexOf('submenu') == -1 ) {
|
1960 |
+
//Insert in the top-level menu.
|
1961 |
+
var selection = $('#ws_menu_box').find('.ws_active');
|
1962 |
+
outputTopMenu(menu, (selection.length > 0) ? selection : null);
|
1963 |
+
} else {
|
1964 |
+
//Insert in the currently visible submenu.
|
1965 |
+
pasteItem(menu);
|
1966 |
+
}
|
1967 |
+
});
|
1968 |
+
|
1969 |
+
//Toggle all menus for the currently selected actor
|
1970 |
+
$('#ws_toggle_all_menus').click(function() {
|
1971 |
+
if ( selectedActor == null ) {
|
1972 |
+
alert("This button enables/disables all menus for the selected role. To use it, click a role and then click this button again.");
|
1973 |
+
return;
|
1974 |
+
}
|
1975 |
+
|
1976 |
+
var topMenuNodes = $('.ws_menu', '#ws_menu_box');
|
1977 |
+
//Look at the first menu's permissions and set everything to the opposite.
|
1978 |
+
var allow = ! actorCanAccessMenu(topMenuNodes.eq(0).data('menu_item'), selectedActor);
|
1979 |
+
|
1980 |
+
topMenuNodes.each(function() {
|
1981 |
+
var containerNode = $(this);
|
1982 |
+
setActorAccessForTreeAndUpdateUi(containerNode, selectedActor, allow);
|
1983 |
+
});
|
1984 |
});
|
1985 |
+
|
1986 |
/*************************************************************************
|
1987 |
Item toolbar buttons
|
1988 |
*************************************************************************/
|
1989 |
+
function getSelectedSubmenuItem() {
|
1990 |
+
return $('#ws_submenu_box').find('.ws_submenu:visible .ws_active');
|
1991 |
+
}
|
1992 |
+
|
1993 |
//Show/Hide item
|
1994 |
$('#ws_hide_item').click(function () {
|
1995 |
//Get the selected item
|
1996 |
+
var selection = getSelectedSubmenuItem();
|
1997 |
if (!selection.length) return;
|
1998 |
+
|
1999 |
//Mark the item as hidden/visible
|
2000 |
+
var menuItem = selection.data('menu_item');
|
2001 |
+
menuItem.hidden = !menuItem.hidden;
|
2002 |
+
setMenuFlag(selection, 'hidden', menuItem.hidden);
|
2003 |
});
|
2004 |
+
|
2005 |
+
//Delete item
|
2006 |
$('#ws_delete_item').click(function () {
|
2007 |
//Get the selected menu
|
2008 |
+
var selection = getSelectedSubmenuItem();
|
2009 |
if (!selection.length) return;
|
2010 |
+
|
2011 |
if (confirm('Delete this menu item?')){
|
2012 |
+
var submenu = selection.parent();
|
2013 |
//Delete the item
|
2014 |
selection.remove();
|
2015 |
+
updateParentAccessUi(submenu);
|
2016 |
}
|
2017 |
});
|
2018 |
+
|
2019 |
//Copy item
|
2020 |
$('#ws_copy_item').click(function () {
|
2021 |
//Get the selected item
|
2022 |
+
var selection = getSelectedSubmenuItem();
|
2023 |
if (!selection.length) return;
|
2024 |
+
|
2025 |
//Store a copy of item state in the clipboard
|
2026 |
+
menu_in_clipboard = readItemState(selection);
|
2027 |
});
|
2028 |
+
|
2029 |
//Cut item
|
2030 |
$('#ws_cut_item').click(function () {
|
2031 |
//Get the selected item
|
2032 |
+
var selection = getSelectedSubmenuItem();
|
2033 |
if (!selection.length) return;
|
2034 |
+
|
2035 |
//Store a copy of item state in the clipboard
|
2036 |
+
menu_in_clipboard = readItemState(selection);
|
2037 |
+
|
2038 |
+
var submenu = selection.parent();
|
2039 |
+
//Remove the original item
|
2040 |
selection.remove();
|
2041 |
+
updateParentAccessUi(submenu);
|
2042 |
});
|
2043 |
+
|
2044 |
//Paste item
|
2045 |
+
function pasteItem(item) {
|
2046 |
+
//We're pasting this item into a sub-menu, so it can't have a sub-menu of its own.
|
2047 |
+
//Instead, any sub-menu items belonging to this item will be pasted after the item.
|
2048 |
+
var newItems = [];
|
2049 |
+
for (var file in item.items) {
|
2050 |
+
if (item.items.hasOwnProperty(file)) {
|
2051 |
+
newItems.push(buildMenuItem(item.items[file], false));
|
2052 |
+
}
|
2053 |
+
}
|
2054 |
+
item.items = [];
|
2055 |
+
|
2056 |
+
newItems.unshift(buildMenuItem(item, false));
|
2057 |
+
|
2058 |
+
//Get the selected menu
|
2059 |
+
var visibleSubmenu = $('#ws_submenu_box').find('.ws_submenu:visible');
|
2060 |
+
var selection = visibleSubmenu.find('.ws_active');
|
2061 |
+
for(var i = 0; i < newItems.length; i++) {
|
2062 |
+
if (selection.length > 0) {
|
2063 |
+
//If an item is selected add the pasted items after it
|
2064 |
+
selection.after(newItems[i]);
|
2065 |
+
} else {
|
2066 |
+
//Otherwise add the pasted items at the end
|
2067 |
+
visibleSubmenu.append(newItems[i]);
|
2068 |
+
}
|
2069 |
+
|
2070 |
+
updateItemEditor(newItems[i]);
|
2071 |
+
newItems[i].show();
|
2072 |
+
}
|
2073 |
+
|
2074 |
+
updateParentAccessUi(visibleSubmenu);
|
2075 |
+
}
|
2076 |
+
|
2077 |
$('#ws_paste_item').click(function () {
|
2078 |
//Check if anything has been copied/cut
|
2079 |
+
if (!menu_in_clipboard) return;
|
2080 |
|
2081 |
+
//You can only add separators to submenus in the Pro version.
|
2082 |
+
if ( menu_in_clipboard.separator && !wsEditorData.wsMenuEditorPro ) {
|
2083 |
+
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2084 |
}
|
2085 |
+
|
2086 |
+
//Paste it.
|
2087 |
+
var item = $.extend(true, {}, menu_in_clipboard);
|
2088 |
+
pasteItem(item);
|
2089 |
});
|
2090 |
+
|
2091 |
//New item
|
2092 |
$('#ws_new_item').click(function () {
|
2093 |
+
if ($('.ws_submenu:visible').length < 1) {
|
2094 |
+
return; //Abort if no submenu visible
|
2095 |
}
|
2096 |
+
|
2097 |
ws_paste_count++;
|
2098 |
+
|
2099 |
+
var entry = $.extend({}, wsEditorData.blankMenuItem, {
|
2100 |
+
custom: true,
|
2101 |
+
template_id : '',
|
2102 |
+
menu_title : 'Custom Item ' + ws_paste_count,
|
2103 |
+
file : randomMenuId(),
|
2104 |
+
items: [],
|
2105 |
+
defaults: itemTemplates.getDefaults('')
|
2106 |
+
});
|
2107 |
+
|
2108 |
+
//Make it accessible to only the currently selected actor.
|
2109 |
+
if (selectedActor != null) {
|
2110 |
+
denyAccessForAllExcept(entry, selectedActor);
|
2111 |
+
}
|
2112 |
+
|
|
|
|
|
|
|
2113 |
var menu = buildMenuItem(entry);
|
2114 |
+
updateItemEditor(menu);
|
2115 |
+
|
2116 |
+
//Insert the item into the currently open submenu.
|
2117 |
+
var visibleSubmenu = $('#ws_submenu_box').find('.ws_submenu:visible');
|
2118 |
+
var selection = visibleSubmenu.find('.ws_active');
|
2119 |
+
if (selection.length > 0) {
|
2120 |
+
selection.after(menu);
|
2121 |
+
} else {
|
2122 |
+
visibleSubmenu.append(menu);
|
2123 |
+
}
|
2124 |
+
|
2125 |
//The items's editbox is always open
|
2126 |
menu.find('.ws_edit_link').click();
|
2127 |
+
|
2128 |
+
updateParentAccessUi(menu);
|
2129 |
});
|
2130 |
|
|
|
|
|
|
|
|
|
2131 |
function compareMenus(a, b){
|
|
|
|
|
|
|
|
|
2132 |
var aTitle = jsTrim( $(a).find('.ws_item_title').text() );
|
2133 |
var bTitle = jsTrim( $(b).find('.ws_item_title').text() );
|
2134 |
+
|
2135 |
aTitle = aTitle.toLowerCase();
|
2136 |
bTitle = bTitle.toLowerCase();
|
2137 |
+
|
2138 |
return aTitle > bTitle ? 1 : -1;
|
2139 |
}
|
2140 |
+
|
2141 |
//Sort items in ascending order
|
2142 |
$('#ws_sort_ascending').click(function () {
|
2143 |
+
var submenu = $('#ws_submenu_box').find('.ws_submenu:visible');
|
2144 |
+
if (submenu.length < 1) {
|
2145 |
+
return; //Abort if no submenu visible
|
2146 |
}
|
2147 |
+
|
2148 |
submenu.find('.ws_container').sort(compareMenus);
|
2149 |
});
|
2150 |
+
|
2151 |
//Sort items in descending order
|
2152 |
$('#ws_sort_descending').click(function () {
|
2153 |
+
var submenu = $('#ws_submenu_box').find('.ws_submenu:visible');
|
2154 |
+
if (submenu.length < 1) {
|
2155 |
+
return; //Abort if no submenu visible
|
2156 |
}
|
2157 |
+
|
2158 |
submenu.find('.ws_container').sort((function(a, b){
|
2159 |
return -compareMenus(a, b);
|
2160 |
}));
|
2161 |
});
|
2162 |
+
|
2163 |
//==============================================
|
2164 |
// Main buttons
|
2165 |
//==============================================
|
2166 |
+
|
2167 |
//Save Changes - encode the current menu as JSON and save
|
2168 |
$('#ws_save_menu').click(function () {
|
2169 |
+
var tree = readMenuTreeState();
|
2170 |
+
|
2171 |
+
function findItemByTemplateId(items, templateId) {
|
2172 |
+
var foundItem = null;
|
2173 |
+
|
2174 |
+
$.each(items, function(index, item) {
|
2175 |
+
if (item.template_id == templateId) {
|
2176 |
+
foundItem = item;
|
2177 |
+
return false;
|
2178 |
+
}
|
2179 |
+
if (item.hasOwnProperty('items') && (item.items.length > 0)) {
|
2180 |
+
foundItem = findItemByTemplateId(item.items, templateId);
|
2181 |
+
if (foundItem != null) {
|
2182 |
+
return false;
|
2183 |
+
}
|
2184 |
+
}
|
2185 |
+
return true;
|
2186 |
+
});
|
2187 |
+
|
2188 |
+
return foundItem;
|
2189 |
+
}
|
2190 |
+
|
2191 |
+
//Abort the save if it would make the editor inaccessible.
|
2192 |
+
if (wsEditorData.wsMenuEditorPro) {
|
2193 |
+
var myMenuItem = findItemByTemplateId(tree.tree, 'options-general.php>menu_editor');
|
2194 |
+
if (myMenuItem == null) {
|
2195 |
+
//This is OK - the missing menu item will be re-inserted automatically.
|
2196 |
+
} else if (!actorCanAccessMenu(myMenuItem, 'user:' + wsEditorData.currentUserLogin)) {
|
2197 |
+
alert(
|
2198 |
+
"Error: This configuration would make you unable to access the menu editor!\n\n" +
|
2199 |
+
"Please click either your role name or \"Current user (" + wsEditorData.currentUserLogin + ")\" "+
|
2200 |
+
"and enable the \"Menu Editor Pro\" menu item."
|
2201 |
+
);
|
2202 |
+
return;
|
2203 |
+
}
|
2204 |
+
}
|
2205 |
+
|
2206 |
+
var data = encodeMenuAsJSON(tree);
|
2207 |
$('#ws_data').val(data);
|
2208 |
+
$('#ws_data_length').val(data.length);
|
2209 |
+
$('#ws_selected_actor').val(selectedActor === null ? '' : selectedActor);
|
2210 |
$('#ws_main_form').submit();
|
2211 |
});
|
2212 |
+
|
2213 |
//Load default menu - load the default WordPress menu
|
2214 |
$('#ws_load_menu').click(function () {
|
2215 |
if (confirm('Are you sure you want to load the default WordPress menu?')){
|
2216 |
+
outputWpMenu(defaultMenu.tree);
|
2217 |
}
|
2218 |
});
|
2219 |
+
|
2220 |
//Reset menu - re-load the custom menu. Discards any changes made by user.
|
2221 |
$('#ws_reset_menu').click(function () {
|
2222 |
if (confirm('Undo all changes made in the current editing session?')){
|
2223 |
+
outputWpMenu(customMenu.tree);
|
2224 |
}
|
2225 |
});
|
2226 |
+
|
2227 |
//Export menu - download the current menu as a file
|
2228 |
+
$('#export_dialog').dialog({
|
2229 |
autoOpen: false,
|
2230 |
closeText: ' ',
|
2231 |
modal: true,
|
2232 |
minHeight: 100
|
2233 |
});
|
2234 |
+
|
2235 |
$('#ws_export_menu').click(function(){
|
2236 |
var button = $(this);
|
2237 |
button.attr('disabled', 'disabled');
|
2238 |
button.val('Exporting...');
|
2239 |
+
|
2240 |
$('#export_complete_notice, #download_menu_button').hide();
|
2241 |
$('#export_progress_notice').show();
|
2242 |
$('#export_dialog').dialog('open');
|
2243 |
+
|
2244 |
//Encode and store the menu for download
|
2245 |
+
var exportData = encodeMenuAsJSON();
|
2246 |
+
|
|
|
|
|
|
|
|
|
|
|
2247 |
$.post(
|
2248 |
+
wsEditorData.adminAjaxUrl,
|
2249 |
{
|
2250 |
'data' : exportData,
|
2251 |
'action' : 'export_custom_menu',
|
2252 |
+
'_ajax_nonce' : wsEditorData.exportMenuNonce
|
2253 |
},
|
2254 |
+
function(data){
|
2255 |
button.val('Export');
|
2256 |
button.removeAttr('disabled');
|
2257 |
+
|
2258 |
if ( typeof data['error'] != 'undefined' ){
|
2259 |
$('#export_dialog').dialog('close');
|
2260 |
alert(data.error);
|
2261 |
}
|
2262 |
+
|
2263 |
if ( (typeof data['download_url'] != 'undefined') && data.download_url ){
|
2264 |
//window.location = data.download_url;
|
2265 |
$('#download_menu_button').attr('href', data.download_url);
|
2270 |
'json'
|
2271 |
);
|
2272 |
});
|
2273 |
+
|
2274 |
$('#ws_cancel_export').click(function(){
|
2275 |
$('#export_dialog').dialog('close');
|
2276 |
});
|
2277 |
+
|
2278 |
$('#download_menu_button').click(function(){
|
2279 |
$('#export_dialog').dialog('close');
|
2280 |
});
|
2281 |
+
|
2282 |
//Import menu - upload an exported menu and show it in the editor
|
2283 |
+
$('#import_dialog').dialog({
|
2284 |
autoOpen: false,
|
2285 |
closeText: ' ',
|
2286 |
modal: true
|
2287 |
});
|
2288 |
+
|
2289 |
$('#ws_cancel_import').click(function(){
|
2290 |
$('#import_dialog').dialog('close');
|
2291 |
});
|
2292 |
+
|
2293 |
$('#ws_import_menu').click(function(){
|
2294 |
$('#import_progress_notice, #import_progress_notice2, #import_complete_notice').hide();
|
2295 |
$('#import_menu_form').resetForm();
|
2296 |
//The "Upload" button is disabled until the user selects a file
|
2297 |
+
$('#ws_start_import').attr('disabled', 'disabled');
|
2298 |
+
|
2299 |
+
var importDialog = $('#import_dialog');
|
2300 |
+
importDialog.find('.hide-when-uploading').show();
|
2301 |
+
importDialog.dialog('open');
|
2302 |
+
});
|
2303 |
+
|
2304 |
$('#import_file_selector').change(function(){
|
2305 |
+
$('#ws_start_import').prop('disabled', ! $(this).val() );
|
|
|
|
|
|
|
|
|
2306 |
});
|
2307 |
+
|
2308 |
//AJAXify the upload form
|
2309 |
$('#import_menu_form').ajaxForm({
|
2310 |
dataType : 'json',
|
2311 |
+
beforeSubmit: function(formData) {
|
2312 |
+
|
2313 |
//Check if the user has selected a file
|
2314 |
for(var i = 0; i < formData.length; i++){
|
2315 |
if ( formData[i].name == 'menu' ){
|
2319 |
}
|
2320 |
}
|
2321 |
}
|
2322 |
+
|
2323 |
+
$('#import_dialog').find('.hide-when-uploading').hide();
|
2324 |
$('#import_progress_notice').show();
|
2325 |
+
|
2326 |
$('#ws_start_import').attr('disabled', 'disabled');
|
2327 |
+
return true;
|
2328 |
},
|
2329 |
success: function(data){
|
2330 |
+
var importDialog = $('#import_dialog');
|
2331 |
+
if ( !importDialog.dialog('isOpen') ){
|
2332 |
//Whoops, the user closed the dialog while the upload was in progress.
|
2333 |
//Discard the response silently.
|
2334 |
+
return;
|
2335 |
+
}
|
2336 |
+
|
2337 |
if ( typeof data['error'] != 'undefined' ){
|
2338 |
alert(data.error);
|
2339 |
//Let the user try again
|
2340 |
$('#import_menu_form').resetForm();
|
2341 |
+
importDialog.find('.hide-when-uploading').show();
|
2342 |
}
|
2343 |
$('#import_progress_notice').hide();
|
2344 |
+
|
2345 |
+
if ( (typeof data['tree'] != 'undefined') && data.tree ){
|
2346 |
//Whee, we got back a (seemingly) valid menu. A veritable miracle!
|
2347 |
//Lets load it into the editor.
|
2348 |
+
var progressNotice = $('#import_progress_notice2').show();
|
2349 |
+
outputWpMenu(data.tree);
|
2350 |
+
progressNotice.hide();
|
2351 |
//Display a success notice, then automatically close the window after a few moments
|
2352 |
$('#import_complete_notice').show();
|
2353 |
setTimeout((function(){
|
2354 |
//Close the import dialog
|
2355 |
$('#import_dialog').dialog('close');
|
2356 |
+
}), 500);
|
2357 |
}
|
2358 |
+
|
2359 |
}
|
2360 |
});
|
2361 |
|
2362 |
+
/*************************************************************************
|
2363 |
+
Drag & drop items between menu levels
|
2364 |
+
*************************************************************************/
|
2365 |
+
|
2366 |
+
if (wsEditorData.wsMenuEditorPro) {
|
2367 |
+
//Allow the user to drag sub-menu items to the top level.
|
2368 |
+
$('#ws_top_menu_dropzone').droppable({
|
2369 |
+
'hoverClass' : 'ws_dropzone_hover',
|
2370 |
+
|
2371 |
+
'accept' : (function(thing){
|
2372 |
+
return thing.hasClass('ws_item');
|
2373 |
+
}),
|
2374 |
+
|
2375 |
+
'drop' : (function(event, ui){
|
2376 |
+
var droppedItemData = readItemState(ui.draggable);
|
2377 |
+
pasteMenu(droppedItemData);
|
2378 |
+
if ( !event.ctrlKey ) {
|
2379 |
+
ui.draggable.remove();
|
2380 |
+
}
|
2381 |
+
})
|
2382 |
+
});
|
2383 |
+
|
2384 |
+
//...and to drag top level menus to a sub-menu.
|
2385 |
+
$('#ws_sub_menu_dropzone').droppable({
|
2386 |
+
'hoverClass' : 'ws_dropzone_hover',
|
2387 |
+
|
2388 |
+
'accept' : (function(thing){
|
2389 |
+
var visibleSubmenu = $('#ws_submenu_box').find('.ws_submenu:visible');
|
2390 |
+
return (
|
2391 |
+
//Accept top-level menus
|
2392 |
+
thing.hasClass('ws_menu') &&
|
2393 |
+
|
2394 |
+
//Prevent users from dropping a menu on its own sub-menu.
|
2395 |
+
(visibleSubmenu.attr('id') != thing.data('submenu_id'))
|
2396 |
+
);
|
2397 |
+
}),
|
2398 |
+
|
2399 |
+
'drop' : (function(event, ui){
|
2400 |
+
var droppedItemData = readItemState(ui.draggable);
|
2401 |
+
pasteItem(droppedItemData);
|
2402 |
+
if ( !event.ctrlKey ) {
|
2403 |
+
ui.draggable.remove();
|
2404 |
+
}
|
2405 |
+
})
|
2406 |
+
});
|
2407 |
+
}
|
2408 |
+
|
2409 |
+
|
2410 |
+
//Set up tooltips
|
2411 |
+
$('.ws_tooltip_trigger').qtip();
|
2412 |
+
|
2413 |
//Flag closed hints as hidden by sending the appropriate AJAX request to the backend.
|
2414 |
$('.ws_hint_close').click(function() {
|
2415 |
var hint = $(this).parents('.ws_hint').first();
|
2423 |
}
|
2424 |
);
|
2425 |
});
|
2426 |
+
|
2427 |
+
|
2428 |
+
/******************************************************************
|
2429 |
+
Actor views
|
2430 |
+
******************************************************************/
|
2431 |
+
|
2432 |
+
//Build the list of available actors
|
2433 |
+
var actorSelector = $('#ws_actor_selector').empty();
|
2434 |
+
actorSelector.append('<li><a href="#" class="current ws_no_actor">All</a></li>');
|
2435 |
+
|
2436 |
+
if (wsEditorData.wsMenuEditorPro) {
|
2437 |
+
for(var actor in wsEditorData.actors) {
|
2438 |
+
if (!wsEditorData.actors.hasOwnProperty(actor)) {
|
2439 |
+
continue;
|
2440 |
+
}
|
2441 |
+
actorSelector.append(
|
2442 |
+
$('<li></li>').append(
|
2443 |
+
$('<a></a>')
|
2444 |
+
.attr('href', '#' + actor)
|
2445 |
+
.text(wsEditorData.actors[actor])
|
2446 |
+
)
|
2447 |
+
);
|
2448 |
+
}
|
2449 |
+
actorSelector.show();
|
2450 |
+
|
2451 |
+
if ( wsEditorData.hasOwnProperty('selectedActor') && wsEditorData.selectedActor ) {
|
2452 |
+
setSelectedActor(wsEditorData.selectedActor);
|
2453 |
+
} else {
|
2454 |
+
setSelectedActor(null);
|
2455 |
+
}
|
2456 |
+
}
|
2457 |
+
|
2458 |
+
$('li a', actorSelector).click(function(event) {
|
2459 |
+
var actor = $(this).attr('href').substring(1);
|
2460 |
+
if (actor == '') {
|
2461 |
+
actor = null;
|
2462 |
+
}
|
2463 |
+
|
2464 |
+
setSelectedActor(actor);
|
2465 |
+
|
2466 |
+
event.preventDefault();
|
2467 |
+
});
|
2468 |
+
|
2469 |
//Finally, show the menu
|
2470 |
+
outputWpMenu(customMenu.tree);
|
2471 |
});
|
2472 |
+
|
2473 |
})(jQuery);
|
2474 |
|
2475 |
//==============================================
|
2479 |
jQuery(function($){
|
2480 |
var screenOptions = $('#ws-ame-screen-meta-contents');
|
2481 |
var checkbox = screenOptions.find('#ws-hide-advanced-settings');
|
2482 |
+
|
2483 |
+
if ( wsEditorData.hideAdvancedSettings ){
|
2484 |
checkbox.attr('checked', 'checked');
|
2485 |
} else {
|
2486 |
checkbox.removeAttr('checked');
|
2487 |
}
|
2488 |
+
|
2489 |
//Update editor state when settings change
|
2490 |
checkbox.click(function(){
|
2491 |
+
wsEditorData.hideAdvancedSettings = $(this).attr('checked'); //Using '$(this)' instead of 'checkbox' due to jQuery bugs
|
2492 |
+
var menuEditorNode = $('#ws_menu_editor');
|
2493 |
+
if ( wsEditorData.hideAdvancedSettings ){
|
2494 |
+
menuEditorNode.find('div.ws_advanced').hide();
|
2495 |
+
menuEditorNode.find('a.ws_toggle_advanced_fields').text(wsEditorData.captionShowAdvanced).show();
|
2496 |
} else {
|
2497 |
+
menuEditorNode.find('div.ws_advanced').show();
|
2498 |
+
menuEditorNode.find('a.ws_toggle_advanced_fields').text(wsEditorData.captionHideAdvanced).hide();
|
2499 |
}
|
2500 |
+
|
2501 |
$.post(
|
2502 |
+
wsEditorData.adminAjaxUrl,
|
2503 |
{
|
2504 |
'action' : 'ws_ame_save_screen_options',
|
2505 |
+
'hide_advanced_settings' : wsEditorData.hideAdvancedSettings ? 1 : 0,
|
2506 |
+
'_ajax_nonce' : wsEditorData.hideAdvancedSettingsNonce
|
2507 |
}
|
2508 |
);
|
2509 |
});
|
2510 |
+
|
2511 |
//Move our options into the screen meta panel
|
2512 |
$('#adv-settings').empty().append(screenOptions.show());
|
2513 |
});
|
menu-editor.php
CHANGED
@@ -3,16 +3,20 @@
|
|
3 |
Plugin Name: Admin Menu Editor
|
4 |
Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
|
5 |
Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
6 |
-
Version: 1.
|
7 |
Author: Janis Elsts
|
8 |
-
Author URI: http://w-shadow.com/
|
9 |
*/
|
10 |
|
|
|
|
|
|
|
|
|
11 |
//Are we running in the Dashboard?
|
12 |
if ( is_admin() ) {
|
13 |
|
14 |
//Load the plugin
|
15 |
-
require
|
16 |
$wp_menu_editor = new WPMenuEditor(__FILE__, 'ws_menu_editor');
|
17 |
|
18 |
-
}//is_admin()
|
3 |
Plugin Name: Admin Menu Editor
|
4 |
Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
|
5 |
Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
6 |
+
Version: 1.3
|
7 |
Author: Janis Elsts
|
8 |
+
Author URI: http://w-shadow.com/blog/
|
9 |
*/
|
10 |
|
11 |
+
if ( include(dirname(__FILE__) . '/includes/version-conflict-check.php') ) {
|
12 |
+
return;
|
13 |
+
}
|
14 |
+
|
15 |
//Are we running in the Dashboard?
|
16 |
if ( is_admin() ) {
|
17 |
|
18 |
//Load the plugin
|
19 |
+
require 'includes/menu-editor-core.php';
|
20 |
$wp_menu_editor = new WPMenuEditor(__FILE__, 'ws_menu_editor');
|
21 |
|
22 |
+
}//is_admin()
|
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.6
|
7 |
-
Stable tag: 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,17 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
|
|
63 |
|
64 |
== Changelog ==
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
= 1.2.2 =
|
67 |
* Replaced a number of icons from the "Silk" set with GPL-compatible alternatives.
|
68 |
* Tested with WP 3.6.
|
3 |
Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
|
4 |
Tags: admin, dashboard, menu, security, wpmu
|
5 |
Requires at least: 3.2
|
6 |
+
Tested up to: 3.6.1
|
7 |
+
Stable tag: 1.3
|
8 |
|
9 |
Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
|
10 |
|
63 |
|
64 |
== Changelog ==
|
65 |
|
66 |
+
= 1.3 =
|
67 |
+
* Added a new settings page that lets you choose whether admin menu settings are per-site or network-wide, as well as specify who can access the plugin. To access this page, go to "Settings -> Menu Editor Pro" and click the small "Settings" link next to the page title.
|
68 |
+
* Added a way to show/hide advanced menu options through the settings page in addition to the "Screen Options" panel.
|
69 |
+
* Added a "Show menu access checks" option to make debugging menu permissions easier.
|
70 |
+
* Added partial WPML support. Now you can translate custom menu titles with WPML.
|
71 |
+
* The plugin will now display an error if you try to activate it when another version of it is already active.
|
72 |
+
* Added a "Target page" dropdown as an alternative to the "URL" field. To enter a custom URL, choose "Custom" from the dropdown.
|
73 |
+
* Fixed the "window title" setting only working for some menu items and not others.
|
74 |
+
* Fixed a number of bugs related to moving plugin menus around.
|
75 |
+
* Changed how the plugin stores menu settings. Note: The new format is not backwards-compatible with version 1.2.2.
|
76 |
+
|
77 |
= 1.2.2 =
|
78 |
* Replaced a number of icons from the "Silk" set with GPL-compatible alternatives.
|
79 |
* Tested with WP 3.6.
|
screenshot-1.png
CHANGED
Binary file
|
screenshot-3.png
CHANGED
Binary file
|
uninstall.php
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
|
3 |
/**
|
4 |
* @author W-Shadow
|
5 |
-
* @copyright
|
6 |
*
|
7 |
* The uninstallation script.
|
8 |
*/
|
@@ -14,7 +14,7 @@ if( defined( 'ABSPATH') && defined('WP_UNINSTALL_PLUGIN') ) {
|
|
14 |
if ( function_exists('delete_site_option') ){
|
15 |
delete_site_option('ws_menu_editor');
|
16 |
}
|
17 |
-
|
18 |
//Remove hint visibility flags
|
19 |
if ( function_exists('delete_metadata') ) {
|
20 |
delete_metadata('user', 0, 'ame_show_hints', '', true);
|
2 |
|
3 |
/**
|
4 |
* @author W-Shadow
|
5 |
+
* @copyright 2009
|
6 |
*
|
7 |
* The uninstallation script.
|
8 |
*/
|
14 |
if ( function_exists('delete_site_option') ){
|
15 |
delete_site_option('ws_menu_editor');
|
16 |
}
|
17 |
+
|
18 |
//Remove hint visibility flags
|
19 |
if ( function_exists('delete_metadata') ) {
|
20 |
delete_metadata('user', 0, 'ame_show_hints', '', true);
|