Version Description
- Added a "Feedback" link.
- Added a dropdown list of all roles and capabilities to the menu editor.
- Added toolbar buttons for sorting menu items alphabetically.
- New "Add separator" button.
- New separator graphics.
- Minimum requirements upped to WP 3.0.
- Compatibility with WP 3.0 MultiSite mode.
- Plugin pages moved to different menus no longer stop working.
- Fixed moved pages not having a window title.
- Hide advanced menu fields by default (can be turned off in Screen Options).
- Changed a lot of UI text to be a bit more intuitive.
- In emergencies, administrators can now reset the custom menu by going to http:://example.com/wp-admin/?reset_admin_menu=1
- Fixed the "Donate" link in readme.txt
- Unbundle the JSON.php JSON parser/encoder and use the built-in class-json.php instead.
- Use the native JSON decoding routine if it's available.
- Replaced the cryptic "Cannot redeclare whatever" activation error message with a more useful one.
Download this release
Release Info
Developer | whiteshadow |
Plugin | Admin Menu Editor |
Version | 1.0 |
Comparing to | |
See all releases |
Code changes from version 0.2 to 1.0
- JSON.php +0 -805
- css/menu-editor.css +580 -0
- images/menu-arrows.png +0 -0
- images/pencil_delete.png +0 -0
- images/pencil_delete_gray.png +0 -0
- images/separator_add.png +0 -0
- images/sort_ascending.png +0 -0
- images/sort_descending.png +0 -0
- images/spinner.gif +0 -0
- images/transparent16.png +0 -0
- images/x-light.png +0 -0
- images/x.png +0 -0
- admin-menu-editor-mu.php → includes/admin-menu-editor-mu.php +5 -2
- includes/menu-editor-core.php +1133 -0
- shadow_plugin_framework.php → includes/shadow_plugin_framework.php +151 -32
- js/jquery.form.js +675 -0
- jquery.json-1.3.js → js/jquery.json-1.3.js +0 -0
- js/jquery.sort.js +65 -0
- js/menu-editor.js +1395 -0
- menu-editor-core.php +0 -653
- menu-editor.css +0 -177
- menu-editor.js +0 -690
- menu-editor.php +3 -8
- readme.txt +43 -15
- uninstall.php +4 -1
JSON.php
DELETED
@@ -1,805 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
3 |
-
|
4 |
-
/**
|
5 |
-
* Converts to and from JSON format.
|
6 |
-
*
|
7 |
-
* JSON (JavaScript Object Notation) is a lightweight data-interchange
|
8 |
-
* format. It is easy for humans to read and write. It is easy for machines
|
9 |
-
* to parse and generate. It is based on a subset of the JavaScript
|
10 |
-
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
|
11 |
-
* This feature can also be found in Python. JSON is a text format that is
|
12 |
-
* completely language independent but uses conventions that are familiar
|
13 |
-
* to programmers of the C-family of languages, including C, C++, C#, Java,
|
14 |
-
* JavaScript, Perl, TCL, and many others. These properties make JSON an
|
15 |
-
* ideal data-interchange language.
|
16 |
-
*
|
17 |
-
* This package provides a simple encoder and decoder for JSON notation. It
|
18 |
-
* is intended for use with client-side Javascript applications that make
|
19 |
-
* use of HTTPRequest to perform server communication functions - data can
|
20 |
-
* be encoded into JSON notation for use in a client-side javascript, or
|
21 |
-
* decoded from incoming Javascript requests. JSON format is native to
|
22 |
-
* Javascript, and can be directly eval()'ed with no further parsing
|
23 |
-
* overhead
|
24 |
-
*
|
25 |
-
* All strings should be in ASCII or UTF-8 format!
|
26 |
-
*
|
27 |
-
* LICENSE: Redistribution and use in source and binary forms, with or
|
28 |
-
* without modification, are permitted provided that the following
|
29 |
-
* conditions are met: Redistributions of source code must retain the
|
30 |
-
* above copyright notice, this list of conditions and the following
|
31 |
-
* disclaimer. Redistributions in binary form must reproduce the above
|
32 |
-
* copyright notice, this list of conditions and the following disclaimer
|
33 |
-
* in the documentation and/or other materials provided with the
|
34 |
-
* distribution.
|
35 |
-
*
|
36 |
-
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
37 |
-
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
38 |
-
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
39 |
-
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
40 |
-
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
41 |
-
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
42 |
-
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
43 |
-
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
44 |
-
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
45 |
-
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
46 |
-
* DAMAGE.
|
47 |
-
*
|
48 |
-
* @category
|
49 |
-
* @package Services_JSON
|
50 |
-
* @author Michal Migurski <mike-json@teczno.com>
|
51 |
-
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
|
52 |
-
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
|
53 |
-
* @copyright 2005 Michal Migurski
|
54 |
-
* @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
|
55 |
-
* @license http://www.opensource.org/licenses/bsd-license.php
|
56 |
-
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
|
57 |
-
*/
|
58 |
-
|
59 |
-
/**
|
60 |
-
* Marker constant for Services_JSON::decode(), used to flag stack state
|
61 |
-
*/
|
62 |
-
define('SERVICES_JSON_SLICE', 1);
|
63 |
-
|
64 |
-
/**
|
65 |
-
* Marker constant for Services_JSON::decode(), used to flag stack state
|
66 |
-
*/
|
67 |
-
define('SERVICES_JSON_IN_STR', 2);
|
68 |
-
|
69 |
-
/**
|
70 |
-
* Marker constant for Services_JSON::decode(), used to flag stack state
|
71 |
-
*/
|
72 |
-
define('SERVICES_JSON_IN_ARR', 3);
|
73 |
-
|
74 |
-
/**
|
75 |
-
* Marker constant for Services_JSON::decode(), used to flag stack state
|
76 |
-
*/
|
77 |
-
define('SERVICES_JSON_IN_OBJ', 4);
|
78 |
-
|
79 |
-
/**
|
80 |
-
* Marker constant for Services_JSON::decode(), used to flag stack state
|
81 |
-
*/
|
82 |
-
define('SERVICES_JSON_IN_CMT', 5);
|
83 |
-
|
84 |
-
/**
|
85 |
-
* Behavior switch for Services_JSON::decode()
|
86 |
-
*/
|
87 |
-
define('SERVICES_JSON_LOOSE_TYPE', 16);
|
88 |
-
|
89 |
-
/**
|
90 |
-
* Behavior switch for Services_JSON::decode()
|
91 |
-
*/
|
92 |
-
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
|
93 |
-
|
94 |
-
/**
|
95 |
-
* Converts to and from JSON format.
|
96 |
-
*
|
97 |
-
* Brief example of use:
|
98 |
-
*
|
99 |
-
* <code>
|
100 |
-
* // create a new instance of Services_JSON
|
101 |
-
* $json = new Services_JSON();
|
102 |
-
*
|
103 |
-
* // convert a complexe value to JSON notation, and send it to the browser
|
104 |
-
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
|
105 |
-
* $output = $json->encode($value);
|
106 |
-
*
|
107 |
-
* print($output);
|
108 |
-
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
|
109 |
-
*
|
110 |
-
* // accept incoming POST data, assumed to be in JSON notation
|
111 |
-
* $input = file_get_contents('php://input', 1000000);
|
112 |
-
* $value = $json->decode($input);
|
113 |
-
* </code>
|
114 |
-
*/
|
115 |
-
class Services_JSON
|
116 |
-
{
|
117 |
-
/**
|
118 |
-
* constructs a new JSON instance
|
119 |
-
*
|
120 |
-
* @param int $use object behavior flags; combine with boolean-OR
|
121 |
-
*
|
122 |
-
* possible values:
|
123 |
-
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
|
124 |
-
* "{...}" syntax creates associative arrays
|
125 |
-
* instead of objects in decode().
|
126 |
-
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
|
127 |
-
* Values which can't be encoded (e.g. resources)
|
128 |
-
* appear as NULL instead of throwing errors.
|
129 |
-
* By default, a deeply-nested resource will
|
130 |
-
* bubble up with an error, so all return values
|
131 |
-
* from encode() should be checked with isError()
|
132 |
-
*/
|
133 |
-
function Services_JSON($use = 0)
|
134 |
-
{
|
135 |
-
$this->use = $use;
|
136 |
-
}
|
137 |
-
|
138 |
-
/**
|
139 |
-
* convert a string from one UTF-16 char to one UTF-8 char
|
140 |
-
*
|
141 |
-
* Normally should be handled by mb_convert_encoding, but
|
142 |
-
* provides a slower PHP-only method for installations
|
143 |
-
* that lack the multibye string extension.
|
144 |
-
*
|
145 |
-
* @param string $utf16 UTF-16 character
|
146 |
-
* @return string UTF-8 character
|
147 |
-
* @access private
|
148 |
-
*/
|
149 |
-
function utf162utf8($utf16)
|
150 |
-
{
|
151 |
-
// oh please oh please oh please oh please oh please
|
152 |
-
if(function_exists('mb_convert_encoding')) {
|
153 |
-
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
|
154 |
-
}
|
155 |
-
|
156 |
-
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
|
157 |
-
|
158 |
-
switch(true) {
|
159 |
-
case ((0x7F & $bytes) == $bytes):
|
160 |
-
// this case should never be reached, because we are in ASCII range
|
161 |
-
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
162 |
-
return chr(0x7F & $bytes);
|
163 |
-
|
164 |
-
case (0x07FF & $bytes) == $bytes:
|
165 |
-
// return a 2-byte UTF-8 character
|
166 |
-
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
167 |
-
return chr(0xC0 | (($bytes >> 6) & 0x1F))
|
168 |
-
. chr(0x80 | ($bytes & 0x3F));
|
169 |
-
|
170 |
-
case (0xFFFF & $bytes) == $bytes:
|
171 |
-
// return a 3-byte UTF-8 character
|
172 |
-
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
173 |
-
return chr(0xE0 | (($bytes >> 12) & 0x0F))
|
174 |
-
. chr(0x80 | (($bytes >> 6) & 0x3F))
|
175 |
-
. chr(0x80 | ($bytes & 0x3F));
|
176 |
-
}
|
177 |
-
|
178 |
-
// ignoring UTF-32 for now, sorry
|
179 |
-
return '';
|
180 |
-
}
|
181 |
-
|
182 |
-
/**
|
183 |
-
* convert a string from one UTF-8 char to one UTF-16 char
|
184 |
-
*
|
185 |
-
* Normally should be handled by mb_convert_encoding, but
|
186 |
-
* provides a slower PHP-only method for installations
|
187 |
-
* that lack the multibye string extension.
|
188 |
-
*
|
189 |
-
* @param string $utf8 UTF-8 character
|
190 |
-
* @return string UTF-16 character
|
191 |
-
* @access private
|
192 |
-
*/
|
193 |
-
function utf82utf16($utf8)
|
194 |
-
{
|
195 |
-
// oh please oh please oh please oh please oh please
|
196 |
-
if(function_exists('mb_convert_encoding')) {
|
197 |
-
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
|
198 |
-
}
|
199 |
-
|
200 |
-
switch(strlen($utf8)) {
|
201 |
-
case 1:
|
202 |
-
// this case should never be reached, because we are in ASCII range
|
203 |
-
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
204 |
-
return $utf8;
|
205 |
-
|
206 |
-
case 2:
|
207 |
-
// return a UTF-16 character from a 2-byte UTF-8 char
|
208 |
-
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
209 |
-
return chr(0x07 & (ord($utf8{0}) >> 2))
|
210 |
-
. chr((0xC0 & (ord($utf8{0}) << 6))
|
211 |
-
| (0x3F & ord($utf8{1})));
|
212 |
-
|
213 |
-
case 3:
|
214 |
-
// return a UTF-16 character from a 3-byte UTF-8 char
|
215 |
-
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
216 |
-
return chr((0xF0 & (ord($utf8{0}) << 4))
|
217 |
-
| (0x0F & (ord($utf8{1}) >> 2)))
|
218 |
-
. chr((0xC0 & (ord($utf8{1}) << 6))
|
219 |
-
| (0x7F & ord($utf8{2})));
|
220 |
-
}
|
221 |
-
|
222 |
-
// ignoring UTF-32 for now, sorry
|
223 |
-
return '';
|
224 |
-
}
|
225 |
-
|
226 |
-
/**
|
227 |
-
* encodes an arbitrary variable into JSON format
|
228 |
-
*
|
229 |
-
* @param mixed $var any number, boolean, string, array, or object to be encoded.
|
230 |
-
* see argument 1 to Services_JSON() above for array-parsing behavior.
|
231 |
-
* if var is a strng, note that encode() always expects it
|
232 |
-
* to be in ASCII or UTF-8 format!
|
233 |
-
*
|
234 |
-
* @return mixed JSON string representation of input var or an error if a problem occurs
|
235 |
-
* @access public
|
236 |
-
*/
|
237 |
-
function encode($var)
|
238 |
-
{
|
239 |
-
switch (gettype($var)) {
|
240 |
-
case 'boolean':
|
241 |
-
return $var ? 'true' : 'false';
|
242 |
-
|
243 |
-
case 'NULL':
|
244 |
-
return 'null';
|
245 |
-
|
246 |
-
case 'integer':
|
247 |
-
return (int) $var;
|
248 |
-
|
249 |
-
case 'double':
|
250 |
-
case 'float':
|
251 |
-
return (float) $var;
|
252 |
-
|
253 |
-
case 'string':
|
254 |
-
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
|
255 |
-
$ascii = '';
|
256 |
-
$strlen_var = strlen($var);
|
257 |
-
|
258 |
-
/*
|
259 |
-
* Iterate over every character in the string,
|
260 |
-
* escaping with a slash or encoding to UTF-8 where necessary
|
261 |
-
*/
|
262 |
-
for ($c = 0; $c < $strlen_var; ++$c) {
|
263 |
-
|
264 |
-
$ord_var_c = ord($var{$c});
|
265 |
-
|
266 |
-
switch (true) {
|
267 |
-
case $ord_var_c == 0x08:
|
268 |
-
$ascii .= '\b';
|
269 |
-
break;
|
270 |
-
case $ord_var_c == 0x09:
|
271 |
-
$ascii .= '\t';
|
272 |
-
break;
|
273 |
-
case $ord_var_c == 0x0A:
|
274 |
-
$ascii .= '\n';
|
275 |
-
break;
|
276 |
-
case $ord_var_c == 0x0C:
|
277 |
-
$ascii .= '\f';
|
278 |
-
break;
|
279 |
-
case $ord_var_c == 0x0D:
|
280 |
-
$ascii .= '\r';
|
281 |
-
break;
|
282 |
-
|
283 |
-
case $ord_var_c == 0x22:
|
284 |
-
case $ord_var_c == 0x2F:
|
285 |
-
case $ord_var_c == 0x5C:
|
286 |
-
// double quote, slash, slosh
|
287 |
-
$ascii .= '\\'.$var{$c};
|
288 |
-
break;
|
289 |
-
|
290 |
-
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
|
291 |
-
// characters U-00000000 - U-0000007F (same as ASCII)
|
292 |
-
$ascii .= $var{$c};
|
293 |
-
break;
|
294 |
-
|
295 |
-
case (($ord_var_c & 0xE0) == 0xC0):
|
296 |
-
// characters U-00000080 - U-000007FF, mask 110XXXXX
|
297 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
298 |
-
$char = pack('C*', $ord_var_c, ord($var{$c + 1}));
|
299 |
-
$c += 1;
|
300 |
-
$utf16 = $this->utf82utf16($char);
|
301 |
-
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
302 |
-
break;
|
303 |
-
|
304 |
-
case (($ord_var_c & 0xF0) == 0xE0):
|
305 |
-
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
|
306 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
307 |
-
$char = pack('C*', $ord_var_c,
|
308 |
-
ord($var{$c + 1}),
|
309 |
-
ord($var{$c + 2}));
|
310 |
-
$c += 2;
|
311 |
-
$utf16 = $this->utf82utf16($char);
|
312 |
-
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
313 |
-
break;
|
314 |
-
|
315 |
-
case (($ord_var_c & 0xF8) == 0xF0):
|
316 |
-
// characters U-00010000 - U-001FFFFF, mask 11110XXX
|
317 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
318 |
-
$char = pack('C*', $ord_var_c,
|
319 |
-
ord($var{$c + 1}),
|
320 |
-
ord($var{$c + 2}),
|
321 |
-
ord($var{$c + 3}));
|
322 |
-
$c += 3;
|
323 |
-
$utf16 = $this->utf82utf16($char);
|
324 |
-
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
325 |
-
break;
|
326 |
-
|
327 |
-
case (($ord_var_c & 0xFC) == 0xF8):
|
328 |
-
// characters U-00200000 - U-03FFFFFF, mask 111110XX
|
329 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
330 |
-
$char = pack('C*', $ord_var_c,
|
331 |
-
ord($var{$c + 1}),
|
332 |
-
ord($var{$c + 2}),
|
333 |
-
ord($var{$c + 3}),
|
334 |
-
ord($var{$c + 4}));
|
335 |
-
$c += 4;
|
336 |
-
$utf16 = $this->utf82utf16($char);
|
337 |
-
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
338 |
-
break;
|
339 |
-
|
340 |
-
case (($ord_var_c & 0xFE) == 0xFC):
|
341 |
-
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
|
342 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
343 |
-
$char = pack('C*', $ord_var_c,
|
344 |
-
ord($var{$c + 1}),
|
345 |
-
ord($var{$c + 2}),
|
346 |
-
ord($var{$c + 3}),
|
347 |
-
ord($var{$c + 4}),
|
348 |
-
ord($var{$c + 5}));
|
349 |
-
$c += 5;
|
350 |
-
$utf16 = $this->utf82utf16($char);
|
351 |
-
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
352 |
-
break;
|
353 |
-
}
|
354 |
-
}
|
355 |
-
|
356 |
-
return '"'.$ascii.'"';
|
357 |
-
|
358 |
-
case 'array':
|
359 |
-
/*
|
360 |
-
* As per JSON spec if any array key is not an integer
|
361 |
-
* we must treat the the whole array as an object. We
|
362 |
-
* also try to catch a sparsely populated associative
|
363 |
-
* array with numeric keys here because some JS engines
|
364 |
-
* will create an array with empty indexes up to
|
365 |
-
* max_index which can cause memory issues and because
|
366 |
-
* the keys, which may be relevant, will be remapped
|
367 |
-
* otherwise.
|
368 |
-
*
|
369 |
-
* As per the ECMA and JSON specification an object may
|
370 |
-
* have any string as a property. Unfortunately due to
|
371 |
-
* a hole in the ECMA specification if the key is a
|
372 |
-
* ECMA reserved word or starts with a digit the
|
373 |
-
* parameter is only accessible using ECMAScript's
|
374 |
-
* bracket notation.
|
375 |
-
*/
|
376 |
-
|
377 |
-
// treat as a JSON object
|
378 |
-
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
|
379 |
-
$properties = array_map(array($this, 'name_value'),
|
380 |
-
array_keys($var),
|
381 |
-
array_values($var));
|
382 |
-
|
383 |
-
foreach($properties as $property) {
|
384 |
-
if(Services_JSON::isError($property)) {
|
385 |
-
return $property;
|
386 |
-
}
|
387 |
-
}
|
388 |
-
|
389 |
-
return '{' . join(',', $properties) . '}';
|
390 |
-
}
|
391 |
-
|
392 |
-
// treat it like a regular array
|
393 |
-
$elements = array_map(array($this, 'encode'), $var);
|
394 |
-
|
395 |
-
foreach($elements as $element) {
|
396 |
-
if(Services_JSON::isError($element)) {
|
397 |
-
return $element;
|
398 |
-
}
|
399 |
-
}
|
400 |
-
|
401 |
-
return '[' . join(',', $elements) . ']';
|
402 |
-
|
403 |
-
case 'object':
|
404 |
-
$vars = get_object_vars($var);
|
405 |
-
|
406 |
-
$properties = array_map(array($this, 'name_value'),
|
407 |
-
array_keys($vars),
|
408 |
-
array_values($vars));
|
409 |
-
|
410 |
-
foreach($properties as $property) {
|
411 |
-
if(Services_JSON::isError($property)) {
|
412 |
-
return $property;
|
413 |
-
}
|
414 |
-
}
|
415 |
-
|
416 |
-
return '{' . join(',', $properties) . '}';
|
417 |
-
|
418 |
-
default:
|
419 |
-
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
|
420 |
-
? 'null'
|
421 |
-
: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
|
422 |
-
}
|
423 |
-
}
|
424 |
-
|
425 |
-
/**
|
426 |
-
* array-walking function for use in generating JSON-formatted name-value pairs
|
427 |
-
*
|
428 |
-
* @param string $name name of key to use
|
429 |
-
* @param mixed $value reference to an array element to be encoded
|
430 |
-
*
|
431 |
-
* @return string JSON-formatted name-value pair, like '"name":value'
|
432 |
-
* @access private
|
433 |
-
*/
|
434 |
-
function name_value($name, $value)
|
435 |
-
{
|
436 |
-
$encoded_value = $this->encode($value);
|
437 |
-
|
438 |
-
if(Services_JSON::isError($encoded_value)) {
|
439 |
-
return $encoded_value;
|
440 |
-
}
|
441 |
-
|
442 |
-
return $this->encode(strval($name)) . ':' . $encoded_value;
|
443 |
-
}
|
444 |
-
|
445 |
-
/**
|
446 |
-
* reduce a string by removing leading and trailing comments and whitespace
|
447 |
-
*
|
448 |
-
* @param $str string string value to strip of comments and whitespace
|
449 |
-
*
|
450 |
-
* @return string string value stripped of comments and whitespace
|
451 |
-
* @access private
|
452 |
-
*/
|
453 |
-
function reduce_string($str)
|
454 |
-
{
|
455 |
-
$str = preg_replace(array(
|
456 |
-
|
457 |
-
// eliminate single line comments in '// ...' form
|
458 |
-
'#^\s*//(.+)$#m',
|
459 |
-
|
460 |
-
// eliminate multi-line comments in '/* ... */' form, at start of string
|
461 |
-
'#^\s*/\*(.+)\*/#Us',
|
462 |
-
|
463 |
-
// eliminate multi-line comments in '/* ... */' form, at end of string
|
464 |
-
'#/\*(.+)\*/\s*$#Us'
|
465 |
-
|
466 |
-
), '', $str);
|
467 |
-
|
468 |
-
// eliminate extraneous space
|
469 |
-
return trim($str);
|
470 |
-
}
|
471 |
-
|
472 |
-
/**
|
473 |
-
* decodes a JSON string into appropriate variable
|
474 |
-
*
|
475 |
-
* @param string $str JSON-formatted string
|
476 |
-
*
|
477 |
-
* @return mixed number, boolean, string, array, or object
|
478 |
-
* corresponding to given JSON input string.
|
479 |
-
* See argument 1 to Services_JSON() above for object-output behavior.
|
480 |
-
* Note that decode() always returns strings
|
481 |
-
* in ASCII or UTF-8 format!
|
482 |
-
* @access public
|
483 |
-
*/
|
484 |
-
function decode($str)
|
485 |
-
{
|
486 |
-
$str = $this->reduce_string($str);
|
487 |
-
|
488 |
-
switch (strtolower($str)) {
|
489 |
-
case 'true':
|
490 |
-
return true;
|
491 |
-
|
492 |
-
case 'false':
|
493 |
-
return false;
|
494 |
-
|
495 |
-
case 'null':
|
496 |
-
return null;
|
497 |
-
|
498 |
-
default:
|
499 |
-
$m = array();
|
500 |
-
|
501 |
-
if (is_numeric($str)) {
|
502 |
-
// Lookie-loo, it's a number
|
503 |
-
|
504 |
-
// This would work on its own, but I'm trying to be
|
505 |
-
// good about returning integers where appropriate:
|
506 |
-
// return (float)$str;
|
507 |
-
|
508 |
-
// Return float or int, as appropriate
|
509 |
-
return ((float)$str == (integer)$str)
|
510 |
-
? (integer)$str
|
511 |
-
: (float)$str;
|
512 |
-
|
513 |
-
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
|
514 |
-
// STRINGS RETURNED IN UTF-8 FORMAT
|
515 |
-
$delim = substr($str, 0, 1);
|
516 |
-
$chrs = substr($str, 1, -1);
|
517 |
-
$utf8 = '';
|
518 |
-
$strlen_chrs = strlen($chrs);
|
519 |
-
|
520 |
-
for ($c = 0; $c < $strlen_chrs; ++$c) {
|
521 |
-
|
522 |
-
$substr_chrs_c_2 = substr($chrs, $c, 2);
|
523 |
-
$ord_chrs_c = ord($chrs{$c});
|
524 |
-
|
525 |
-
switch (true) {
|
526 |
-
case $substr_chrs_c_2 == '\b':
|
527 |
-
$utf8 .= chr(0x08);
|
528 |
-
++$c;
|
529 |
-
break;
|
530 |
-
case $substr_chrs_c_2 == '\t':
|
531 |
-
$utf8 .= chr(0x09);
|
532 |
-
++$c;
|
533 |
-
break;
|
534 |
-
case $substr_chrs_c_2 == '\n':
|
535 |
-
$utf8 .= chr(0x0A);
|
536 |
-
++$c;
|
537 |
-
break;
|
538 |
-
case $substr_chrs_c_2 == '\f':
|
539 |
-
$utf8 .= chr(0x0C);
|
540 |
-
++$c;
|
541 |
-
break;
|
542 |
-
case $substr_chrs_c_2 == '\r':
|
543 |
-
$utf8 .= chr(0x0D);
|
544 |
-
++$c;
|
545 |
-
break;
|
546 |
-
|
547 |
-
case $substr_chrs_c_2 == '\\"':
|
548 |
-
case $substr_chrs_c_2 == '\\\'':
|
549 |
-
case $substr_chrs_c_2 == '\\\\':
|
550 |
-
case $substr_chrs_c_2 == '\\/':
|
551 |
-
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
|
552 |
-
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
|
553 |
-
$utf8 .= $chrs{++$c};
|
554 |
-
}
|
555 |
-
break;
|
556 |
-
|
557 |
-
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
|
558 |
-
// single, escaped unicode character
|
559 |
-
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
|
560 |
-
. chr(hexdec(substr($chrs, ($c + 4), 2)));
|
561 |
-
$utf8 .= $this->utf162utf8($utf16);
|
562 |
-
$c += 5;
|
563 |
-
break;
|
564 |
-
|
565 |
-
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
|
566 |
-
$utf8 .= $chrs{$c};
|
567 |
-
break;
|
568 |
-
|
569 |
-
case ($ord_chrs_c & 0xE0) == 0xC0:
|
570 |
-
// characters U-00000080 - U-000007FF, mask 110XXXXX
|
571 |
-
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
572 |
-
$utf8 .= substr($chrs, $c, 2);
|
573 |
-
++$c;
|
574 |
-
break;
|
575 |
-
|
576 |
-
case ($ord_chrs_c & 0xF0) == 0xE0:
|
577 |
-
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
|
578 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
579 |
-
$utf8 .= substr($chrs, $c, 3);
|
580 |
-
$c += 2;
|
581 |
-
break;
|
582 |
-
|
583 |
-
case ($ord_chrs_c & 0xF8) == 0xF0:
|
584 |
-
// characters U-00010000 - U-001FFFFF, mask 11110XXX
|
585 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
586 |
-
$utf8 .= substr($chrs, $c, 4);
|
587 |
-
$c += 3;
|
588 |
-
break;
|
589 |
-
|
590 |
-
case ($ord_chrs_c & 0xFC) == 0xF8:
|
591 |
-
// characters U-00200000 - U-03FFFFFF, mask 111110XX
|
592 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
593 |
-
$utf8 .= substr($chrs, $c, 5);
|
594 |
-
$c += 4;
|
595 |
-
break;
|
596 |
-
|
597 |
-
case ($ord_chrs_c & 0xFE) == 0xFC:
|
598 |
-
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
|
599 |
-
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
600 |
-
$utf8 .= substr($chrs, $c, 6);
|
601 |
-
$c += 5;
|
602 |
-
break;
|
603 |
-
|
604 |
-
}
|
605 |
-
|
606 |
-
}
|
607 |
-
|
608 |
-
return $utf8;
|
609 |
-
|
610 |
-
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
|
611 |
-
// array, or object notation
|
612 |
-
|
613 |
-
if ($str{0} == '[') {
|
614 |
-
$stk = array(SERVICES_JSON_IN_ARR);
|
615 |
-
$arr = array();
|
616 |
-
} else {
|
617 |
-
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
618 |
-
$stk = array(SERVICES_JSON_IN_OBJ);
|
619 |
-
$obj = array();
|
620 |
-
} else {
|
621 |
-
$stk = array(SERVICES_JSON_IN_OBJ);
|
622 |
-
$obj = new stdClass();
|
623 |
-
}
|
624 |
-
}
|
625 |
-
|
626 |
-
array_push($stk, array('what' => SERVICES_JSON_SLICE,
|
627 |
-
'where' => 0,
|
628 |
-
'delim' => false));
|
629 |
-
|
630 |
-
$chrs = substr($str, 1, -1);
|
631 |
-
$chrs = $this->reduce_string($chrs);
|
632 |
-
|
633 |
-
if ($chrs == '') {
|
634 |
-
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
635 |
-
return $arr;
|
636 |
-
|
637 |
-
} else {
|
638 |
-
return $obj;
|
639 |
-
|
640 |
-
}
|
641 |
-
}
|
642 |
-
|
643 |
-
//print("\nparsing {$chrs}\n");
|
644 |
-
|
645 |
-
$strlen_chrs = strlen($chrs);
|
646 |
-
|
647 |
-
for ($c = 0; $c <= $strlen_chrs; ++$c) {
|
648 |
-
|
649 |
-
$top = end($stk);
|
650 |
-
$substr_chrs_c_2 = substr($chrs, $c, 2);
|
651 |
-
|
652 |
-
if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
|
653 |
-
// found a comma that is not inside a string, array, etc.,
|
654 |
-
// OR we've reached the end of the character list
|
655 |
-
$slice = substr($chrs, $top['where'], ($c - $top['where']));
|
656 |
-
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
|
657 |
-
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
658 |
-
|
659 |
-
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
660 |
-
// we are in an array, so just push an element onto the stack
|
661 |
-
array_push($arr, $this->decode($slice));
|
662 |
-
|
663 |
-
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
|
664 |
-
// we are in an object, so figure
|
665 |
-
// out the property name and set an
|
666 |
-
// element in an associative array,
|
667 |
-
// for now
|
668 |
-
$parts = array();
|
669 |
-
|
670 |
-
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
|
671 |
-
// "name":value pair
|
672 |
-
$key = $this->decode($parts[1]);
|
673 |
-
$val = $this->decode($parts[2]);
|
674 |
-
|
675 |
-
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
676 |
-
$obj[$key] = $val;
|
677 |
-
} else {
|
678 |
-
$obj->$key = $val;
|
679 |
-
}
|
680 |
-
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
|
681 |
-
// name:value pair, where name is unquoted
|
682 |
-
$key = $parts[1];
|
683 |
-
$val = $this->decode($parts[2]);
|
684 |
-
|
685 |
-
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
686 |
-
$obj[$key] = $val;
|
687 |
-
} else {
|
688 |
-
$obj->$key = $val;
|
689 |
-
}
|
690 |
-
}
|
691 |
-
|
692 |
-
}
|
693 |
-
|
694 |
-
} elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
|
695 |
-
// found a quote, and we are not inside a string
|
696 |
-
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
|
697 |
-
//print("Found start of string at {$c}\n");
|
698 |
-
|
699 |
-
} elseif (($chrs{$c} == $top['delim']) &&
|
700 |
-
($top['what'] == SERVICES_JSON_IN_STR) &&
|
701 |
-
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
|
702 |
-
// found a quote, we're in a string, and it's not escaped
|
703 |
-
// we know that it's not escaped becase there is _not_ an
|
704 |
-
// odd number of backslashes at the end of the string so far
|
705 |
-
array_pop($stk);
|
706 |
-
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
|
707 |
-
|
708 |
-
} elseif (($chrs{$c} == '[') &&
|
709 |
-
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
710 |
-
// found a left-bracket, and we are in an array, object, or slice
|
711 |
-
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
|
712 |
-
//print("Found start of array at {$c}\n");
|
713 |
-
|
714 |
-
} elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
|
715 |
-
// found a right-bracket, and we're in an array
|
716 |
-
array_pop($stk);
|
717 |
-
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
718 |
-
|
719 |
-
} elseif (($chrs{$c} == '{') &&
|
720 |
-
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
721 |
-
// found a left-brace, and we are in an array, object, or slice
|
722 |
-
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
|
723 |
-
//print("Found start of object at {$c}\n");
|
724 |
-
|
725 |
-
} elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
|
726 |
-
// found a right-brace, and we're in an object
|
727 |
-
array_pop($stk);
|
728 |
-
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
729 |
-
|
730 |
-
} elseif (($substr_chrs_c_2 == '/*') &&
|
731 |
-
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
732 |
-
// found a comment start, and we are in an array, object, or slice
|
733 |
-
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
|
734 |
-
$c++;
|
735 |
-
//print("Found start of comment at {$c}\n");
|
736 |
-
|
737 |
-
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
|
738 |
-
// found a comment end, and we're in one now
|
739 |
-
array_pop($stk);
|
740 |
-
$c++;
|
741 |
-
|
742 |
-
for ($i = $top['where']; $i <= $c; ++$i)
|
743 |
-
$chrs = substr_replace($chrs, ' ', $i, 1);
|
744 |
-
|
745 |
-
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
746 |
-
|
747 |
-
}
|
748 |
-
|
749 |
-
}
|
750 |
-
|
751 |
-
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
752 |
-
return $arr;
|
753 |
-
|
754 |
-
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
|
755 |
-
return $obj;
|
756 |
-
|
757 |
-
}
|
758 |
-
|
759 |
-
}
|
760 |
-
}
|
761 |
-
}
|
762 |
-
|
763 |
-
/**
|
764 |
-
* @todo Ultimately, this should just call PEAR::isError()
|
765 |
-
*/
|
766 |
-
function isError($data, $code = null)
|
767 |
-
{
|
768 |
-
if (class_exists('pear')) {
|
769 |
-
return PEAR::isError($data, $code);
|
770 |
-
} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
|
771 |
-
is_subclass_of($data, 'services_json_error'))) {
|
772 |
-
return true;
|
773 |
-
}
|
774 |
-
|
775 |
-
return false;
|
776 |
-
}
|
777 |
-
}
|
778 |
-
|
779 |
-
if (class_exists('PEAR_Error')) {
|
780 |
-
|
781 |
-
class Services_JSON_Error extends PEAR_Error
|
782 |
-
{
|
783 |
-
function Services_JSON_Error($message = 'unknown error', $code = null,
|
784 |
-
$mode = null, $options = null, $userinfo = null)
|
785 |
-
{
|
786 |
-
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
|
787 |
-
}
|
788 |
-
}
|
789 |
-
|
790 |
-
} else {
|
791 |
-
|
792 |
-
/**
|
793 |
-
* @todo Ultimately, this class shall be descended from PEAR_Error
|
794 |
-
*/
|
795 |
-
class Services_JSON_Error
|
796 |
-
{
|
797 |
-
function Services_JSON_Error($message = 'unknown error', $code = null,
|
798 |
-
$mode = null, $options = null, $userinfo = null)
|
799 |
-
{
|
800 |
-
|
801 |
-
}
|
802 |
-
}
|
803 |
-
|
804 |
-
}
|
805 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
css/menu-editor.css
ADDED
@@ -0,0 +1,580 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Admin Menu Editor CSS file */
|
2 |
+
|
3 |
+
.ws_main_container {
|
4 |
+
margin: 2px;
|
5 |
+
width: 310px;
|
6 |
+
float: left;
|
7 |
+
display:block;
|
8 |
+
border: 1px solid #cdd5d5;
|
9 |
+
|
10 |
+
border-radius: 3px;
|
11 |
+
-moz-border-radius: 3px;
|
12 |
+
-webkit-border-radius: 3px;
|
13 |
+
}
|
14 |
+
|
15 |
+
.ws_box {
|
16 |
+
min-height: 30px;
|
17 |
+
width: 100%;
|
18 |
+
margin: 0;
|
19 |
+
padding-top: 2px;
|
20 |
+
padding-bottom: 4px;
|
21 |
+
}
|
22 |
+
|
23 |
+
#ws_menu_box {
|
24 |
+
}
|
25 |
+
|
26 |
+
#ws_submenu_box {
|
27 |
+
}
|
28 |
+
|
29 |
+
/*
|
30 |
+
* The sidebar
|
31 |
+
*/
|
32 |
+
|
33 |
+
#ws_editor_sidebar {
|
34 |
+
width: 138px;
|
35 |
+
padding: 2px;
|
36 |
+
}
|
37 |
+
|
38 |
+
.ws_main_button {
|
39 |
+
clear: both;
|
40 |
+
display: block;
|
41 |
+
margin: 4px;
|
42 |
+
margin-left: auto;
|
43 |
+
margin-right: auto;
|
44 |
+
width: 120px;
|
45 |
+
padding: 4px !important;
|
46 |
+
}
|
47 |
+
|
48 |
+
#ws_save_menu {
|
49 |
+
margin-bottom: 20px;
|
50 |
+
}
|
51 |
+
|
52 |
+
#ws_export_menu {
|
53 |
+
margin-top: 12px;
|
54 |
+
}
|
55 |
+
|
56 |
+
/*
|
57 |
+
* Menu components and widgets
|
58 |
+
*/
|
59 |
+
|
60 |
+
.ws_container {
|
61 |
+
display: block;
|
62 |
+
width: 290px;
|
63 |
+
|
64 |
+
padding : 3px;
|
65 |
+
margin: 2px;
|
66 |
+
margin-left: auto;
|
67 |
+
margin-right: auto;
|
68 |
+
|
69 |
+
border: 1px solid #a9badb;
|
70 |
+
background-color: #bdd3ff;
|
71 |
+
}
|
72 |
+
|
73 |
+
.ws_active {
|
74 |
+
background-color : #8eb0f1 !important; /* make sure this overrides ws_menu_separator */
|
75 |
+
}
|
76 |
+
|
77 |
+
.ws_menu { }
|
78 |
+
.ws_item { }
|
79 |
+
|
80 |
+
.ws_menu_separator {
|
81 |
+
background-image: url("../images/menu-arrows.png");
|
82 |
+
background-repeat: no-repeat;
|
83 |
+
background-position : 4px 8px;
|
84 |
+
background-color: #F9F9F9;
|
85 |
+
border-color: #d9d9d9;
|
86 |
+
}
|
87 |
+
|
88 |
+
.ws_submenu {
|
89 |
+
min-height: 2em;
|
90 |
+
}
|
91 |
+
|
92 |
+
|
93 |
+
.ws_item_head {
|
94 |
+
padding: 0;
|
95 |
+
}
|
96 |
+
|
97 |
+
.ws_item_title {
|
98 |
+
display: block;
|
99 |
+
padding: 2px;
|
100 |
+
cursor: default;
|
101 |
+
}
|
102 |
+
|
103 |
+
.ws_edit_link {
|
104 |
+
float: right;
|
105 |
+
margin-right: 0px;
|
106 |
+
cursor: pointer;
|
107 |
+
display:block;
|
108 |
+
width: 40px;
|
109 |
+
height: 22px;
|
110 |
+
|
111 |
+
background-image: url('../images/bullet_arrow_down2.png');
|
112 |
+
background-repeat: no-repeat;
|
113 |
+
background-position: center;
|
114 |
+
|
115 |
+
border-top-right-radius: 3px;
|
116 |
+
border-top-left-radius: 3px;
|
117 |
+
|
118 |
+
-moz-border-radius-topright: 3px;
|
119 |
+
-moz-border-radius-topleft: 3px;
|
120 |
+
|
121 |
+
-webkit-border-top-right-radius: 3px;
|
122 |
+
-webkit-border-top-left-radius: 3px;
|
123 |
+
}
|
124 |
+
|
125 |
+
a.ws_edit_link:hover {
|
126 |
+
background-color: #ffffd0;
|
127 |
+
background-image: url('../images/bullet_arrow_down2.png');
|
128 |
+
}
|
129 |
+
|
130 |
+
.ws_edit_link_expanded {
|
131 |
+
background-color: #ffffd0;
|
132 |
+
border-bottom: none;
|
133 |
+
border-color: #ffffd0;
|
134 |
+
|
135 |
+
background-image: url('../images/bullet_arrow_down2.png');
|
136 |
+
padding-bottom: 1px;
|
137 |
+
background-position: center 3px;
|
138 |
+
}
|
139 |
+
|
140 |
+
/****************************************
|
141 |
+
Per-menu settings fields & panels
|
142 |
+
*****************************************/
|
143 |
+
|
144 |
+
.ws_editbox {
|
145 |
+
display: block;
|
146 |
+
background-color: #ffffd0;
|
147 |
+
padding: 4px;
|
148 |
+
|
149 |
+
border-radius: 2px;
|
150 |
+
border-top-right-radius: 0px;
|
151 |
+
|
152 |
+
-moz-border-radius: 2px;
|
153 |
+
-moz-border-radius-topright: 0px;
|
154 |
+
|
155 |
+
-webkit-border-radius: 2px;
|
156 |
+
-webkit-border-top-right-radius: 0px;
|
157 |
+
}
|
158 |
+
|
159 |
+
.ws_edit_panel {
|
160 |
+
margin: 0;
|
161 |
+
padding: 0;
|
162 |
+
border: none;
|
163 |
+
}
|
164 |
+
|
165 |
+
.ws_edit_field {
|
166 |
+
margin-bottom: 8px;
|
167 |
+
height: 42.2px;
|
168 |
+
}
|
169 |
+
|
170 |
+
.ws_edit_field-custom {
|
171 |
+
margin-top: 10px;
|
172 |
+
}
|
173 |
+
|
174 |
+
/* The reset-to-default button */
|
175 |
+
.ws_reset_button {
|
176 |
+
display: block;
|
177 |
+
float: right;
|
178 |
+
|
179 |
+
margin-left: 4px;
|
180 |
+
margin-top: 2px;
|
181 |
+
margin-right: 6px;
|
182 |
+
cursor: pointer;
|
183 |
+
|
184 |
+
width: 16px;
|
185 |
+
height: 16px;
|
186 |
+
vertical-align: top;
|
187 |
+
|
188 |
+
background-image: url("../images/pencil_delete_gray.png");
|
189 |
+
background-repeat: no-repeat;
|
190 |
+
background-position: center;
|
191 |
+
}
|
192 |
+
|
193 |
+
.ws_reset_button:hover {
|
194 |
+
background-image: url("../images/pencil_delete.png");
|
195 |
+
}
|
196 |
+
|
197 |
+
.ws_input_default input, .ws_input_default select {
|
198 |
+
color: gray;
|
199 |
+
}
|
200 |
+
|
201 |
+
/* No reset button for fields set to the default value */
|
202 |
+
.ws_input_default .ws_reset_button {
|
203 |
+
visibility: hidden;
|
204 |
+
}
|
205 |
+
|
206 |
+
/* The input box in each field editor */
|
207 |
+
#ws_menu_editor .ws_editbox input[type="text"],
|
208 |
+
#ws_menu_editor .ws_editbox select {
|
209 |
+
display: block;
|
210 |
+
float: left;
|
211 |
+
width: 254px;
|
212 |
+
|
213 |
+
font-size: 12px;
|
214 |
+
padding: 3px;
|
215 |
+
}
|
216 |
+
|
217 |
+
#ws_menu_editor .ws_edit_field label {
|
218 |
+
display: block;
|
219 |
+
float: left;
|
220 |
+
}
|
221 |
+
|
222 |
+
#ws_menu_editor .ws_edit_field-custom input[type="checkbox"] {
|
223 |
+
margin-top: 0px;
|
224 |
+
}
|
225 |
+
|
226 |
+
/* Dropdown button for the capability field */
|
227 |
+
#ws_menu_editor .ws_dropdown_button {
|
228 |
+
display : block;
|
229 |
+
float: left;
|
230 |
+
|
231 |
+
width: 20px;
|
232 |
+
height: 20px;
|
233 |
+
|
234 |
+
margin: 1px 1px 1px 0;
|
235 |
+
padding: 0;
|
236 |
+
|
237 |
+
text-align: center;
|
238 |
+
vertical-align: middle;
|
239 |
+
font-size: 9px !important;
|
240 |
+
|
241 |
+
border-color: #dfdfdf;
|
242 |
+
|
243 |
+
border-top-right-radius: 3px;
|
244 |
+
border-bottom-right-radius: 3px;
|
245 |
+
border-top-left-radius: 0px;
|
246 |
+
border-bottom-left-radius: 0px;
|
247 |
+
|
248 |
+
-moz-border-radius-topright: 3px;
|
249 |
+
-moz-border-radius-bottomright: 3px;
|
250 |
+
-moz-border-radius-topleft: 0px;
|
251 |
+
-moz-border-radius-bottomleft: 0px;
|
252 |
+
|
253 |
+
-webkit-border-top-right-radius: 3px;
|
254 |
+
-webkit-border-bottom-right-radius: 3px;
|
255 |
+
-webkit-border-top-left-radius: 0px;
|
256 |
+
-webkit-border-bottom-left-radius: 0px;
|
257 |
+
}
|
258 |
+
|
259 |
+
/*
|
260 |
+
The capability field's appearance and size need to be changed
|
261 |
+
to accomodate the dropdown button.
|
262 |
+
*/
|
263 |
+
#ws_menu_editor .ws_edit_field-access_level input.ws_field_value {
|
264 |
+
width: 230px;
|
265 |
+
margin-right: 0;
|
266 |
+
border-right: 0;
|
267 |
+
|
268 |
+
border-top-right-radius: 0px;
|
269 |
+
border-bottom-right-radius: 0px;
|
270 |
+
|
271 |
+
-moz-border-radius-topright: 0px;
|
272 |
+
-moz-border-radius-bottomright: 0px;
|
273 |
+
|
274 |
+
-webkit-border-top-right-radius: 0px;
|
275 |
+
-webkit-border-bottom-right-radius: 0px;
|
276 |
+
}
|
277 |
+
|
278 |
+
/* Unlike others, this field is just a single checkbox, so it has a smaller height */
|
279 |
+
#ws_menu_editor .ws_edit_field-custom {
|
280 |
+
height: 16px;
|
281 |
+
}
|
282 |
+
|
283 |
+
/*
|
284 |
+
* "Show/hide advanced fields"
|
285 |
+
*/
|
286 |
+
.ws_toggle_container {
|
287 |
+
text-align: right;
|
288 |
+
margin-right: 27px;
|
289 |
+
}
|
290 |
+
|
291 |
+
.ws_toggle_advanced_fields {
|
292 |
+
color: #6087CB;
|
293 |
+
text-decoration: none;
|
294 |
+
font-size: 0.85em;
|
295 |
+
}
|
296 |
+
|
297 |
+
.ws_toggle_advanced_fields:visited, .ws_toggle_advanced_fields:active {
|
298 |
+
color: #6087CB;
|
299 |
+
}
|
300 |
+
|
301 |
+
.ws_toggle_advanced_fields:hover {
|
302 |
+
color: #d54e21;
|
303 |
+
text-decoration: underline;
|
304 |
+
}
|
305 |
+
|
306 |
+
/************************************
|
307 |
+
Menu flags
|
308 |
+
*************************************/
|
309 |
+
|
310 |
+
.ws_flag_container {
|
311 |
+
float: right;
|
312 |
+
margin-right: 4px;
|
313 |
+
padding-top: 2px;
|
314 |
+
}
|
315 |
+
|
316 |
+
.ws_flag {
|
317 |
+
display: block;
|
318 |
+
float: right;
|
319 |
+
width: 16px;
|
320 |
+
height: 16px;
|
321 |
+
margin-left: 4px;
|
322 |
+
background-repeat: no-repeat;
|
323 |
+
}
|
324 |
+
|
325 |
+
/* user-created items */
|
326 |
+
.ws_custom_item_flag {
|
327 |
+
background-image: url('../images/page_white_add.png');
|
328 |
+
}
|
329 |
+
|
330 |
+
/* items not present in the default menu */
|
331 |
+
.ws_missing_flag {
|
332 |
+
background-image: url('../images/plugin_error.png');
|
333 |
+
}
|
334 |
+
|
335 |
+
/* unused items - those that are in the default menu but not in the custom one */
|
336 |
+
.ws_unused_flag {
|
337 |
+
background-image: url('../images/plugin_add.png');
|
338 |
+
}
|
339 |
+
|
340 |
+
/* hidden items */
|
341 |
+
.ws_hidden_flag {
|
342 |
+
background-image: url('../images/plugin_disabled.png');
|
343 |
+
}
|
344 |
+
|
345 |
+
/* These classes could be used to apply different styles to items depending on their flags */
|
346 |
+
.ws_missing { }
|
347 |
+
.ws_custom_item { }
|
348 |
+
.ws_hidden { }
|
349 |
+
.ws_unused { }
|
350 |
+
|
351 |
+
|
352 |
+
/************************************
|
353 |
+
Toolbars
|
354 |
+
*************************************/
|
355 |
+
|
356 |
+
.ws_toolbar {
|
357 |
+
display: block;
|
358 |
+
width: 100%;
|
359 |
+
height: 34px;
|
360 |
+
}
|
361 |
+
|
362 |
+
.ws_button_container {
|
363 |
+
padding-left: 6px;
|
364 |
+
padding-top: 6px;
|
365 |
+
}
|
366 |
+
|
367 |
+
.ws_button {
|
368 |
+
display: block;
|
369 |
+
margin-right: 3px;
|
370 |
+
padding: 4px;
|
371 |
+
border: 1px solid #c0c0e0;
|
372 |
+
float: left;
|
373 |
+
|
374 |
+
border-radius: 3px;
|
375 |
+
-moz-border-radius: 3px;
|
376 |
+
-webkit-border-radius: 3px;
|
377 |
+
}
|
378 |
+
|
379 |
+
a.ws_button:hover {
|
380 |
+
background-color: #d0e0ff;
|
381 |
+
border-color: #9090c0;
|
382 |
+
}
|
383 |
+
|
384 |
+
.ws_separator {
|
385 |
+
float: left;
|
386 |
+
width: 5px;
|
387 |
+
}
|
388 |
+
|
389 |
+
/************************************
|
390 |
+
Capability selector
|
391 |
+
*************************************/
|
392 |
+
|
393 |
+
select#ws_cap_selector {
|
394 |
+
width: 252px;
|
395 |
+
height: 20em;
|
396 |
+
|
397 |
+
z-index: 1002;
|
398 |
+
position: absolute;
|
399 |
+
display: none;
|
400 |
+
|
401 |
+
font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
|
402 |
+
font-size: 12px;
|
403 |
+
}
|
404 |
+
|
405 |
+
select#ws_cap_selector option {
|
406 |
+
font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
|
407 |
+
font-size: 12px;
|
408 |
+
padding: 3px;
|
409 |
+
}
|
410 |
+
|
411 |
+
select#ws_cap_selector optgroup option {
|
412 |
+
padding-left: 10px;
|
413 |
+
}
|
414 |
+
|
415 |
+
/************************************
|
416 |
+
Export and import
|
417 |
+
*************************************/
|
418 |
+
|
419 |
+
#export_dialog, #import_dialog {
|
420 |
+
display: none;
|
421 |
+
}
|
422 |
+
|
423 |
+
.ui-widget-overlay {
|
424 |
+
background-color: black;
|
425 |
+
position: absolute;
|
426 |
+
left: 0px;
|
427 |
+
top: 0px;
|
428 |
+
opacity: 0.70;
|
429 |
+
-moz-opacity: 0.70;
|
430 |
+
filter: alpha(opacity=70);
|
431 |
+
}
|
432 |
+
|
433 |
+
.ui-dialog {
|
434 |
+
background: white;
|
435 |
+
border: 1px solid #c0c0c0;
|
436 |
+
|
437 |
+
padding: 2px;
|
438 |
+
|
439 |
+
border-radius: 5px;
|
440 |
+
-moz-border-radius: 5px;
|
441 |
+
-webkit-border-radius: 5px;
|
442 |
+
}
|
443 |
+
|
444 |
+
.ui-dialog-titlebar {
|
445 |
+
display: block;
|
446 |
+
height: 22px;
|
447 |
+
margin: 0;
|
448 |
+
padding: 4px 4px 4px 8px;
|
449 |
+
|
450 |
+
background-color: #86A7E3;
|
451 |
+
font-size: 1.0em;
|
452 |
+
line-height: 22px;
|
453 |
+
|
454 |
+
border-radius: 5px;
|
455 |
+
-moz-border-radius: 5px;
|
456 |
+
-webkit-border-radius: 5px;
|
457 |
+
}
|
458 |
+
|
459 |
+
.ui-dialog-title {
|
460 |
+
color: white;
|
461 |
+
font-weight: bold;
|
462 |
+
}
|
463 |
+
|
464 |
+
.ui-dialog-titlebar-close {
|
465 |
+
background-image: url(../images/x.png);
|
466 |
+
background-repeat: no-repeat;
|
467 |
+
background-position: center;
|
468 |
+
background-color: #86A7E3;
|
469 |
+
|
470 |
+
width: 22px;
|
471 |
+
height: 22px;
|
472 |
+
display: block;
|
473 |
+
float: right;
|
474 |
+
color: white;
|
475 |
+
|
476 |
+
border-radius: 3px;
|
477 |
+
-moz-border-radius: 3px;
|
478 |
+
-webkit-border-radius: 3px;
|
479 |
+
}
|
480 |
+
|
481 |
+
.ui-dialog-titlebar-close:hover {
|
482 |
+
/*background-image: url(../images/x-light.png);*/
|
483 |
+
background-color: #a6c2f5;
|
484 |
+
}
|
485 |
+
|
486 |
+
.ui-icon-closethick {
|
487 |
+
|
488 |
+
}
|
489 |
+
|
490 |
+
.ui-dialog-content {
|
491 |
+
padding: 6px;
|
492 |
+
font-size: 1.1em;
|
493 |
+
}
|
494 |
+
|
495 |
+
.ws_dialog_panel {
|
496 |
+
height: 84px;
|
497 |
+
}
|
498 |
+
|
499 |
+
#export_dialog .ws_dialog_panel {
|
500 |
+
height: 70px;
|
501 |
+
}
|
502 |
+
|
503 |
+
.ws_dialog_buttons {
|
504 |
+
height: 23px;
|
505 |
+
text-align: right;
|
506 |
+
}
|
507 |
+
|
508 |
+
.ws_dialog_buttons .button-primary {
|
509 |
+
display: block;
|
510 |
+
float: left;
|
511 |
+
margin-top: 0px;
|
512 |
+
}
|
513 |
+
|
514 |
+
.ws_dialog_buttons .button {
|
515 |
+
margin-top: 0px;
|
516 |
+
}
|
517 |
+
|
518 |
+
#import_file_selector {
|
519 |
+
display: block;
|
520 |
+
width: 286px;
|
521 |
+
|
522 |
+
margin-top: 6px;
|
523 |
+
margin-bottom: 12px;
|
524 |
+
margin-left: auto;
|
525 |
+
margin-right: auto;
|
526 |
+
}
|
527 |
+
|
528 |
+
#ws_start_import {
|
529 |
+
min-width: 100px;
|
530 |
+
}
|
531 |
+
|
532 |
+
#import_complete_notice {
|
533 |
+
text-align: center;
|
534 |
+
font-size: large;
|
535 |
+
padding-top: 25px;
|
536 |
+
}
|
537 |
+
|
538 |
+
/************************************
|
539 |
+
Screen meta buttons
|
540 |
+
*************************************/
|
541 |
+
|
542 |
+
/* All buttons */
|
543 |
+
#ws-ame-feedback-widget-wrap,
|
544 |
+
#ws-pro-version-notice {
|
545 |
+
float: right;
|
546 |
+
height: 22px;
|
547 |
+
padding: 0;
|
548 |
+
margin: 0 6px 0 0;
|
549 |
+
font-family: "Lucida Grande", Verdana, Arial, "Bitstream Vera Sans", sans-serif;
|
550 |
+
background: #e3e3e3; /* original gray */
|
551 |
+
|
552 |
+
border-bottom-left-radius: 3px;
|
553 |
+
border-bottom-right-radius: 3px;
|
554 |
+
-moz-border-radius-bottomleft: 3px;
|
555 |
+
-moz-border-radius-bottomright: 3px;
|
556 |
+
-webkit-border-bottom-left-radius: 3px;
|
557 |
+
-webkit-border-bottom-right-radius: 3px;
|
558 |
+
}
|
559 |
+
|
560 |
+
#ws-ame-feedback-widget-wrap a.show-settings,
|
561 |
+
#ws-pro-version-notice a.show-settings {
|
562 |
+
background-image: none;
|
563 |
+
padding:0 6px 0 6px;
|
564 |
+
}
|
565 |
+
|
566 |
+
/* "Upgrade to Pro" */
|
567 |
+
#ws-pro-version-notice {
|
568 |
+
background-color: #00C31F;
|
569 |
+
}
|
570 |
+
|
571 |
+
|
572 |
+
#ws-pro-version-notice a.show-settings {
|
573 |
+
font-weight: bold;
|
574 |
+
color: #DEFFD8;
|
575 |
+
text-shadow: none;
|
576 |
+
}
|
577 |
+
|
578 |
+
#ws-pro-version-notice a.show-settings:hover {
|
579 |
+
color: white;
|
580 |
+
}
|
images/menu-arrows.png
ADDED
Binary file
|
images/pencil_delete.png
ADDED
Binary file
|
images/pencil_delete_gray.png
ADDED
Binary file
|
images/separator_add.png
ADDED
Binary file
|
images/sort_ascending.png
ADDED
Binary file
|
images/sort_descending.png
ADDED
Binary file
|
images/spinner.gif
ADDED
Binary file
|
images/transparent16.png
ADDED
Binary file
|
images/x-light.png
ADDED
Binary file
|
images/x.png
ADDED
Binary file
|
admin-menu-editor-mu.php → includes/admin-menu-editor-mu.php
RENAMED
@@ -18,9 +18,12 @@ mu-plugins/
|
|
18 |
**/
|
19 |
|
20 |
//Load the plugin
|
21 |
-
$ws_menu_editor_filename = dirname(__FILE__) . '/admin-menu-editor/menu-editor.php';
|
|
|
22 |
if ( file_exists($ws_menu_editor_filename) ) {
|
23 |
require $ws_menu_editor_filename;
|
|
|
|
|
24 |
} else {
|
25 |
add_action('admin_notices', 'ws_ame_installation_error');
|
26 |
}
|
@@ -34,7 +37,7 @@ function ws_ame_installation_error(){
|
|
34 |
<p>
|
35 |
Please copy the entire <code>admin-menu-directory</code> directory to your <code>mu-plugins</code>
|
36 |
directory, then move only the admin-menu-editor-mu.php file from
|
37 |
-
<code>admin-menu-editor</code> to <code>mu-plugins</code>.
|
38 |
</p>
|
39 |
</div>
|
40 |
<?php
|
18 |
**/
|
19 |
|
20 |
//Load the plugin
|
21 |
+
$ws_menu_editor_filename = dirname(__FILE__) . '/admin-menu-editor/menu-editor.php';
|
22 |
+
$ws_menu_editor_pro_filename = dirname(__FILE__) . '/admin-menu-editor-pro/menu-editor.php';
|
23 |
if ( file_exists($ws_menu_editor_filename) ) {
|
24 |
require $ws_menu_editor_filename;
|
25 |
+
} elseif ( file_exists($ws_menu_editor_pro_filename) ) {
|
26 |
+
require $ws_menu_editor_pro_filename;
|
27 |
} else {
|
28 |
add_action('admin_notices', 'ws_ame_installation_error');
|
29 |
}
|
37 |
<p>
|
38 |
Please copy the entire <code>admin-menu-directory</code> directory to your <code>mu-plugins</code>
|
39 |
directory, then move only the admin-menu-editor-mu.php file from
|
40 |
+
<code>admin-menu-editor/includes</code> to <code>mu-plugins</code>.
|
41 |
</p>
|
42 |
</div>
|
43 |
<?php
|
includes/menu-editor-core.php
ADDED
@@ -0,0 +1,1133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
//Can't have two different versions of the plugin active at the same time. It would be incredibly buggy.
|
4 |
+
if (class_exists('WPMenuEditor')){
|
5 |
+
trigger_error(
|
6 |
+
'Another version of Admin Menu Editor is already active. Please deactivate it before activating this one.',
|
7 |
+
E_USER_ERROR
|
8 |
+
);
|
9 |
+
}
|
10 |
+
|
11 |
+
//Load the "framework"
|
12 |
+
require 'shadow_plugin_framework.php';
|
13 |
+
|
14 |
+
if ( !class_exists('WPMenuEditor') ) :
|
15 |
+
|
16 |
+
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
17 |
+
|
18 |
+
protected $default_wp_menu = null; //Holds the default WP menu for later use in the editor
|
19 |
+
protected $default_wp_submenu = null; //Holds the default WP menu for later use
|
20 |
+
protected $title_lookups = array(); //A list of page titles indexed by $item['file']. Used to
|
21 |
+
//fix the titles of moved plugin pages.
|
22 |
+
private $custom_menu = null; //The current custom menu with defaults merged in
|
23 |
+
public $menu_format_version = 4;
|
24 |
+
|
25 |
+
private $templates = null; //Template arrays for various menu structures. See the constructor for details.
|
26 |
+
|
27 |
+
function init(){
|
28 |
+
//Determine if the plugin is active network-wide (i.e. either installed in
|
29 |
+
//the /mu-plugins/ directory or activated "network wide" by the super admin.
|
30 |
+
if ( $this->is_super_plugin() ){
|
31 |
+
$this->sitewide_options = true;
|
32 |
+
}
|
33 |
+
|
34 |
+
//Set some plugin-specific options
|
35 |
+
if ( empty($this->option_name) ){
|
36 |
+
$this->option_name = 'ws_menu_editor';
|
37 |
+
}
|
38 |
+
$this->defaults = array(
|
39 |
+
'hide_advanced_settings' => true,
|
40 |
+
'menu_format_version' => 0,
|
41 |
+
);
|
42 |
+
$this->serialize_with_json = false; //(Don't) store the options in JSON format
|
43 |
+
|
44 |
+
$this->settings_link = 'options-general.php?page=menu_editor';
|
45 |
+
|
46 |
+
$this->magic_hooks = true;
|
47 |
+
$this->magic_hook_priority = 99999;
|
48 |
+
|
49 |
+
//Build some template arrays
|
50 |
+
$this->templates['basic_defaults'] = array(
|
51 |
+
'page_title' => '',
|
52 |
+
'menu_title' => '',
|
53 |
+
'access_level' => 'read',
|
54 |
+
'file' => '',
|
55 |
+
'css_class' => '',
|
56 |
+
'hookname' => '',
|
57 |
+
'icon_url' => '',
|
58 |
+
'position' => 0,
|
59 |
+
'separator' => false,
|
60 |
+
'custom' => false,
|
61 |
+
'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
|
62 |
+
);
|
63 |
+
|
64 |
+
//Template for a basic top-level menu
|
65 |
+
$this->templates['blank_menu'] = array(
|
66 |
+
'page_title' => null,
|
67 |
+
'menu_title' => null,
|
68 |
+
'access_level' => null,
|
69 |
+
'file' => null,
|
70 |
+
'css_class' => null,
|
71 |
+
'hookname' => null,
|
72 |
+
'icon_url' => null,
|
73 |
+
'position' => null,
|
74 |
+
'separator' => null,
|
75 |
+
'custom' => null,
|
76 |
+
'open_in' => null,
|
77 |
+
'defaults' => $this->templates['basic_defaults'],
|
78 |
+
'items' => array(),
|
79 |
+
);
|
80 |
+
//Template for menu items
|
81 |
+
$this->templates['blank_item'] = array(
|
82 |
+
'menu_title' => null,
|
83 |
+
'access_level' => null,
|
84 |
+
'file' => null,
|
85 |
+
'page_title' => null,
|
86 |
+
'position' => null,
|
87 |
+
'custom' => null,
|
88 |
+
'open_in' => null,
|
89 |
+
'defaults' => $this->templates['basic_defaults'],
|
90 |
+
);
|
91 |
+
|
92 |
+
//AJAXify screen options
|
93 |
+
add_action( 'wp_ajax_ws_ame_save_screen_options', array(&$this,'ajax_save_screen_options') );
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Activation hook
|
98 |
+
*
|
99 |
+
* @return void
|
100 |
+
*/
|
101 |
+
function activate(){
|
102 |
+
//If we have no stored settings for this version of the plugin, try importing them
|
103 |
+
//from other versions (i.e. the free or the Pro version).
|
104 |
+
if ( !$this->load_options() ){
|
105 |
+
$this->import_settings();
|
106 |
+
}
|
107 |
+
|
108 |
+
parent::activate();
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Import settings from a different version of the plugin.
|
113 |
+
*
|
114 |
+
* @return bool True if settings were imported successfully, False otherwise
|
115 |
+
*/
|
116 |
+
function import_settings(){
|
117 |
+
$possible_names = array('ws_menu_editor', 'ws_menu_editor_pro');
|
118 |
+
foreach($possible_names as $option_name){
|
119 |
+
if ( $this->load_options($option_name) ){
|
120 |
+
return true;
|
121 |
+
}
|
122 |
+
}
|
123 |
+
|
124 |
+
return false;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Add the JS required by the editor to the page header
|
129 |
+
*
|
130 |
+
* @return void
|
131 |
+
*/
|
132 |
+
function enqueue_scripts(){
|
133 |
+
wp_enqueue_script('jquery');
|
134 |
+
wp_enqueue_script('jquery-ui-sortable');
|
135 |
+
wp_enqueue_script('jquery-ui-dialog');
|
136 |
+
wp_enqueue_script('jquery-form');
|
137 |
+
|
138 |
+
//jQuery JSON plugin
|
139 |
+
wp_enqueue_script('jquery-json', $this->plugin_dir_url.'/js/jquery.json-1.3.js', array('jquery'), '1.3');
|
140 |
+
//jQuery sort plugin
|
141 |
+
wp_enqueue_script('jquery-sort', $this->plugin_dir_url.'/js/jquery.sort.js', array('jquery'));
|
142 |
+
|
143 |
+
//Editor's scipts
|
144 |
+
wp_enqueue_script('menu-editor', $this->plugin_dir_url.'/js/menu-editor.js', array('jquery'), '1.0');
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Add the editor's CSS file to the page header
|
149 |
+
*
|
150 |
+
* @return void
|
151 |
+
*/
|
152 |
+
function enqueue_styles(){
|
153 |
+
wp_enqueue_style('menu-editor-style', $this->plugin_dir_url . '/css/menu-editor.css', array(), '1.0');
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Create a configuration page and load the custom menu
|
158 |
+
*
|
159 |
+
* @return void
|
160 |
+
*/
|
161 |
+
function hook_admin_menu(){
|
162 |
+
global $menu, $submenu;
|
163 |
+
|
164 |
+
//Menu reset (for emergencies). Executed by accessing http://example.com/wp-admin/?reset_admin_menu=1
|
165 |
+
$reset_requested = isset($_GET['reset_admin_menu']) && $_GET['reset_admin_menu'];
|
166 |
+
if ( $reset_requested && $this->current_user_can_edit_menu() ){
|
167 |
+
$this->options['custom_menu'] = null;
|
168 |
+
$this->save_options();
|
169 |
+
}
|
170 |
+
|
171 |
+
//The menu editor is only visible to users with the manage_options privilege.
|
172 |
+
//Or, if the plugin is installed in mu-plugins, only to the site administrator(s).
|
173 |
+
if ( $this->current_user_can_edit_menu() ){
|
174 |
+
$page = add_options_page(
|
175 |
+
apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'),
|
176 |
+
apply_filters('admin_menu_editor-self_menu_title', 'Menu Editor'),
|
177 |
+
'manage_options',
|
178 |
+
'menu_editor',
|
179 |
+
array(&$this, 'page_menu_editor')
|
180 |
+
);
|
181 |
+
//Output our JS & CSS on that page only
|
182 |
+
add_action("admin_print_scripts-$page", array(&$this, 'enqueue_scripts'));
|
183 |
+
add_action("admin_print_styles-$page", array(&$this, 'enqueue_styles'));
|
184 |
+
|
185 |
+
//Make a placeholder for our screen options (hacky)
|
186 |
+
add_meta_box("ws-ame-screen-options", "You should never see this", array(&$this, 'noop'), $page);
|
187 |
+
}
|
188 |
+
|
189 |
+
//WP 3.0 in multisite mode has two separators with the same filename. This plugin
|
190 |
+
//expects all top-level menus to have unique filenames/URLs.
|
191 |
+
$first_separator1 = -1;
|
192 |
+
$last_separator1 = -1;
|
193 |
+
foreach($menu as $index => $item){
|
194 |
+
if ( $item[2] == 'separator1' ){
|
195 |
+
$last_separator1 = $index;
|
196 |
+
if ( $first_separator1 == -1 ){
|
197 |
+
$first_separator1 = $index;
|
198 |
+
}
|
199 |
+
}
|
200 |
+
}
|
201 |
+
if ( $first_separator1 != $last_separator1 ){
|
202 |
+
$menu[$first_separator1][2] = 'separator0';
|
203 |
+
}
|
204 |
+
|
205 |
+
//Store the "original" menus for later use in the editor
|
206 |
+
$this->default_wp_menu = $menu;
|
207 |
+
$this->default_wp_submenu = $submenu;
|
208 |
+
|
209 |
+
//Is there a custom menu to use?
|
210 |
+
if ( !empty($this->options['custom_menu']) ){
|
211 |
+
//Check if we need to upgrade the menu structure
|
212 |
+
if ( empty($this->options['menu_format_version']) || ($this->options['menu_format_version'] < $this->menu_format_version) ){
|
213 |
+
$this->options['custom_menu'] = $this->upgrade_menu_structure($this->options['custom_menu']);
|
214 |
+
$this->options['menu_format_version'] = $this->menu_format_version;
|
215 |
+
$this->save_options();
|
216 |
+
}
|
217 |
+
//Merge in data from the default menu
|
218 |
+
$tree = $this->menu_merge($this->options['custom_menu'], $menu, $submenu);
|
219 |
+
//Apply the custom menu
|
220 |
+
list($menu, $submenu, $this->title_lookups) = $this->tree2wp($tree);
|
221 |
+
//Save for later - the editor page will need it
|
222 |
+
$this->custom_menu = $tree;
|
223 |
+
//Re-filter the menu (silly WP should do that itself, oh well)
|
224 |
+
$this->filter_menu();
|
225 |
+
}
|
226 |
+
}
|
227 |
+
|
228 |
+
/**
|
229 |
+
* Determine if the current user may use the menu editor.
|
230 |
+
*
|
231 |
+
* @return bool
|
232 |
+
*/
|
233 |
+
function current_user_can_edit_menu(){
|
234 |
+
if ( $this->is_super_plugin() ){
|
235 |
+
return is_super_admin();
|
236 |
+
} else {
|
237 |
+
return current_user_can('manage_options');
|
238 |
+
}
|
239 |
+
}
|
240 |
+
|
241 |
+
/**
|
242 |
+
* Intercept a handy action to fix the page title for moved plugin pages.
|
243 |
+
*
|
244 |
+
* @return void
|
245 |
+
*/
|
246 |
+
function hook_admin_xml_ns(){
|
247 |
+
global $title;
|
248 |
+
global $pagenow;
|
249 |
+
global $plugin_page;
|
250 |
+
|
251 |
+
if ( empty($title) && !empty($plugin_page) && !empty($pagenow) ){
|
252 |
+
$file = sprintf('%s?page=%s', $pagenow, $plugin_page);
|
253 |
+
if ( isset($this->title_lookups[$file]) ){
|
254 |
+
$title = esc_html( strip_tags( $this->title_lookups[$file] ) );
|
255 |
+
}
|
256 |
+
}
|
257 |
+
}
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Loop over the Dashboard submenus and remove pages for which the current user does not have privs.
|
261 |
+
*
|
262 |
+
* @return void
|
263 |
+
*/
|
264 |
+
function filter_menu(){
|
265 |
+
global $menu, $submenu, $_wp_submenu_nopriv, $_wp_menu_nopriv;
|
266 |
+
|
267 |
+
foreach ( array( 'submenu' ) as $sub_loop ) {
|
268 |
+
foreach ($$sub_loop as $parent => $sub) {
|
269 |
+
foreach ($sub as $index => $data) {
|
270 |
+
if ( ! current_user_can($data[1]) ) {
|
271 |
+
unset(${$sub_loop}[$parent][$index]);
|
272 |
+
$_wp_submenu_nopriv[$parent][$data[2]] = true;
|
273 |
+
}
|
274 |
+
}
|
275 |
+
|
276 |
+
if ( empty(${$sub_loop}[$parent]) )
|
277 |
+
unset(${$sub_loop}[$parent]);
|
278 |
+
}
|
279 |
+
}
|
280 |
+
}
|
281 |
+
|
282 |
+
/**
|
283 |
+
* Encode a menu tree as JSON
|
284 |
+
*
|
285 |
+
* @param array $tree
|
286 |
+
* @return string
|
287 |
+
*/
|
288 |
+
function getMenuAsJS($tree){
|
289 |
+
return $this->json_encode($tree);
|
290 |
+
}
|
291 |
+
|
292 |
+
/**
|
293 |
+
* Convert a WP menu structure to an associative array
|
294 |
+
*
|
295 |
+
* @param array $item An element of the $menu array
|
296 |
+
* @param integer $pos The position (index) of the menu item
|
297 |
+
* @return array
|
298 |
+
*/
|
299 |
+
function menu2assoc($item, $pos=0){
|
300 |
+
$item = array(
|
301 |
+
'menu_title' => $item[0],
|
302 |
+
'access_level' => $item[1],
|
303 |
+
'file' => $item[2],
|
304 |
+
'page_title' => $item[3],
|
305 |
+
'css_class' => $item[4],
|
306 |
+
'hookname' => (isset($item[5])?$item[5]:''), //ID
|
307 |
+
'icon_url' => (isset($item[6])?$item[6]:''),
|
308 |
+
'position' => $pos,
|
309 |
+
);
|
310 |
+
$item['separator'] = strpos($item['css_class'], 'wp-menu-separator') !== false;
|
311 |
+
//Flag plugin pages
|
312 |
+
$item['is_plugin_page'] = (get_plugin_page_hook($item['file'], '') != null);
|
313 |
+
|
314 |
+
return array_merge($this->templates['basic_defaults'], $item);
|
315 |
+
}
|
316 |
+
|
317 |
+
/**
|
318 |
+
* Convert a WP submenu structure to an associative array
|
319 |
+
*
|
320 |
+
* @param array $item An element of the $submenu array
|
321 |
+
* @param integer $pos The position (index) of that element
|
322 |
+
* @param string $parent Parent file that this menu item belongs to.
|
323 |
+
* @return array
|
324 |
+
*/
|
325 |
+
function submenu2assoc($item, $pos = 0, $parent = ''){
|
326 |
+
$item = array(
|
327 |
+
'menu_title' => $item[0],
|
328 |
+
'access_level' => $item[1],
|
329 |
+
'file' => $item[2],
|
330 |
+
'page_title' => (isset($item[3])?$item[3]:''),
|
331 |
+
'position' => $pos,
|
332 |
+
);
|
333 |
+
//Save the default parent menu
|
334 |
+
$item['parent'] = $parent;
|
335 |
+
//Flag plugin pages
|
336 |
+
$item['is_plugin_page'] = (get_plugin_page_hook($item['file'], $parent) != null);
|
337 |
+
|
338 |
+
return array_merge($this->templates['basic_defaults'], $item);
|
339 |
+
}
|
340 |
+
|
341 |
+
/**
|
342 |
+
* Populate lookup arrays with default values from $menu and $submenu. Used later to merge
|
343 |
+
* a custom menu with the native WordPress menu structure somewhat gracefully.
|
344 |
+
*
|
345 |
+
* @param array $menu
|
346 |
+
* @param array $submenu
|
347 |
+
* @return array An array with two elements containing menu and submenu defaults.
|
348 |
+
*/
|
349 |
+
function build_lookups($menu, $submenu){
|
350 |
+
//Process the top menu
|
351 |
+
$menu_defaults = array();
|
352 |
+
foreach($menu as $pos => $item){
|
353 |
+
$item = $this->menu2assoc($item, $pos);
|
354 |
+
$menu_defaults[$item['file']] = $item; //index by filename
|
355 |
+
}
|
356 |
+
|
357 |
+
//Process the submenu
|
358 |
+
$submenu_defaults = array();
|
359 |
+
foreach($submenu as $parent => $items){
|
360 |
+
foreach($items as $pos => $item){
|
361 |
+
$item = $this->submenu2assoc($item, $pos, $parent);
|
362 |
+
//File itself is not guaranteed to be unique, so we use a surrogate ID to identify submenus.
|
363 |
+
$uid = $this->unique_submenu_id($item['file'], $parent);
|
364 |
+
$submenu_defaults[$uid] = $item;
|
365 |
+
}
|
366 |
+
}
|
367 |
+
|
368 |
+
return array($menu_defaults, $submenu_defaults);
|
369 |
+
}
|
370 |
+
|
371 |
+
/**
|
372 |
+
* Merge $menu and $submenu into the $tree. Adds/replaces defaults, inserts new items
|
373 |
+
* and marks missing items as such.
|
374 |
+
*
|
375 |
+
* @param array $tree A menu in plugin's internal form
|
376 |
+
* @param array $menu WordPress menu structure
|
377 |
+
* @param array $submenu WordPress submenu structure
|
378 |
+
* @return array Updated menu tree
|
379 |
+
*/
|
380 |
+
function menu_merge($tree, $menu, $submenu){
|
381 |
+
list($menu_defaults, $submenu_defaults) = $this->build_lookups($menu, $submenu);
|
382 |
+
|
383 |
+
//Iterate over all menus and submenus and look up default values
|
384 |
+
foreach ($tree as &$topmenu){
|
385 |
+
$topfile = $this->get_menu_field($topmenu, 'file');
|
386 |
+
//Is this menu present in the default WP menu?
|
387 |
+
if (isset($menu_defaults[$topfile])){
|
388 |
+
//Yes, load defaults from that item
|
389 |
+
$topmenu['defaults'] = $menu_defaults[$topfile];
|
390 |
+
//Note that the original item was used
|
391 |
+
$menu_defaults[$topfile]['used'] = true;
|
392 |
+
} else {
|
393 |
+
//Record the menu as missing, unless it's a menu separator
|
394 |
+
if ( empty($topmenu['separator']) ){
|
395 |
+
$topmenu['missing'] = true;
|
396 |
+
//[Nasty] Fill the 'defaults' array for menu's that don't have it.
|
397 |
+
//This should never be required - saving a custom menu should set the defaults
|
398 |
+
//for all menus it contains automatically.
|
399 |
+
if ( empty($topmenu['defaults']) ){
|
400 |
+
$tmp = $topmenu;
|
401 |
+
$topmenu['defaults'] = $tmp;
|
402 |
+
}
|
403 |
+
}
|
404 |
+
}
|
405 |
+
|
406 |
+
if (is_array($topmenu['items'])) {
|
407 |
+
//Iterate over submenu items
|
408 |
+
foreach ($topmenu['items'] as $file => &$item){
|
409 |
+
$uid = $this->unique_submenu_id($item, $topfile);
|
410 |
+
|
411 |
+
//Is this item present in the default WP menu?
|
412 |
+
if (isset($submenu_defaults[$uid])){
|
413 |
+
//Yes, load defaults from that item
|
414 |
+
$item['defaults'] = $submenu_defaults[$uid];
|
415 |
+
$submenu_defaults[$uid]['used'] = true;
|
416 |
+
} else {
|
417 |
+
//Record as missing
|
418 |
+
$item['missing'] = true;
|
419 |
+
if ( empty($item['defaults']) ){
|
420 |
+
$tmp = $item;
|
421 |
+
$item['defaults'] = $tmp;
|
422 |
+
}
|
423 |
+
}
|
424 |
+
}
|
425 |
+
}
|
426 |
+
}
|
427 |
+
|
428 |
+
//If we don't unset these they will fuck up the next two loops where the same names are used.
|
429 |
+
unset($topmenu);
|
430 |
+
unset($item);
|
431 |
+
|
432 |
+
//Note : Now we have some items marked as missing, and some items in lookup arrays
|
433 |
+
//that are not marked as used. The missing items are handled elsewhere (e.g. tree2wp()),
|
434 |
+
//but lets merge in the unused items now.
|
435 |
+
|
436 |
+
//Find and merge unused toplevel menus
|
437 |
+
foreach ($menu_defaults as $topfile => $topmenu){
|
438 |
+
//Skip used menus and separators
|
439 |
+
if ( !empty($topmenu['used']) || !empty($topmenu['separator'])) {
|
440 |
+
continue;
|
441 |
+
};
|
442 |
+
|
443 |
+
//Found an unused item. Build the tree entry.
|
444 |
+
$entry = $this->templates['blank_menu'];
|
445 |
+
$entry['defaults'] = $topmenu;
|
446 |
+
$entry['items'] = array(); //prepare a place for menu items, if any.
|
447 |
+
//Note that this item is unused
|
448 |
+
$entry['unused'] = true;
|
449 |
+
//Add the new entry to the menu tree
|
450 |
+
$tree[$topfile] = $entry;
|
451 |
+
}
|
452 |
+
unset($topmenu);
|
453 |
+
|
454 |
+
//Find and merge submenu items
|
455 |
+
foreach($submenu_defaults as $uid => $item){
|
456 |
+
if ( !empty($item['used']) ) continue;
|
457 |
+
//Found an unused item. Build an entry and attach it under the default toplevel menu.
|
458 |
+
$entry = $this->templates['blank_item'];
|
459 |
+
$entry['defaults'] = $item;
|
460 |
+
//Note that this item is unused
|
461 |
+
$entry['unused'] = true;
|
462 |
+
|
463 |
+
//Check if the toplevel menu exists
|
464 |
+
if (isset($tree[$item['parent']])) {
|
465 |
+
//Okay, insert the item.
|
466 |
+
$tree[$item['parent']]['items'][$item['file']] = $entry;
|
467 |
+
} else {
|
468 |
+
//Ooops? This should never happen. Some kind of inconsistency?
|
469 |
+
}
|
470 |
+
}
|
471 |
+
|
472 |
+
//Resort the tree to ensure the found items are in the right spots
|
473 |
+
$tree = $this->sort_menu_tree($tree);
|
474 |
+
|
475 |
+
return $tree;
|
476 |
+
}
|
477 |
+
|
478 |
+
/**
|
479 |
+
* Generate an ID that uniquely identifies a given submenu item.
|
480 |
+
*
|
481 |
+
* @param string|array $file Menu item in question
|
482 |
+
* @param string $parent Parent menu. Optional. If $file is an array, the function will try to get the parent value from $file['defaults'] instead.
|
483 |
+
* @return string Unique ID
|
484 |
+
*/
|
485 |
+
function unique_submenu_id($file, $parent = ''){
|
486 |
+
if ( is_array($file) ){
|
487 |
+
if ( isset($file['defaults']) && isset($file['defaults']['parent']) ){
|
488 |
+
$parent = $file['defaults']['parent'];
|
489 |
+
}
|
490 |
+
$file = $this->get_menu_field($file, 'file');
|
491 |
+
}
|
492 |
+
|
493 |
+
if ( !empty($parent) ){
|
494 |
+
return $parent . '::' . $file;
|
495 |
+
} else {
|
496 |
+
return $file;
|
497 |
+
}
|
498 |
+
}
|
499 |
+
|
500 |
+
/**
|
501 |
+
* Convert the WP menu structure to the internal representation. All properties set as defaults.
|
502 |
+
*
|
503 |
+
* @param array $menu
|
504 |
+
* @param array $submenu
|
505 |
+
* @return array Menu in the internal tree format.
|
506 |
+
*/
|
507 |
+
function wp2tree($menu, $submenu){
|
508 |
+
$tree = array();
|
509 |
+
$separator_count = 0;
|
510 |
+
foreach ($menu as $pos => $item){
|
511 |
+
|
512 |
+
$tree_item = $this->templates['blank_menu'];
|
513 |
+
$tree_item['defaults'] = $this->menu2assoc($item, $pos);
|
514 |
+
$tree_item['separator'] = empty($item[2]) || empty($item[0]) || (strpos($item[4], 'wp-menu-separator') !== false);
|
515 |
+
|
516 |
+
if ( empty($tree_item['defaults']['file']) ){
|
517 |
+
$tree_item['defaults']['file'] = 'separator_'.$separator_count;
|
518 |
+
$separator_count++;
|
519 |
+
}
|
520 |
+
|
521 |
+
//Attach submenu items
|
522 |
+
$parent = $tree_item['defaults']['file'];
|
523 |
+
if ( isset($submenu[$parent]) ){
|
524 |
+
foreach($submenu[$parent] as $pos => $subitem){
|
525 |
+
$tree_item['items'][$subitem[2]] = array_merge(
|
526 |
+
$this->templates['blank_item'],
|
527 |
+
array('defaults' => $this->submenu2assoc($subitem, $pos, $parent))
|
528 |
+
);
|
529 |
+
}
|
530 |
+
}
|
531 |
+
|
532 |
+
$tree[$parent] = $tree_item;
|
533 |
+
}
|
534 |
+
|
535 |
+
$tree = $this->sort_menu_tree($tree);
|
536 |
+
|
537 |
+
return $tree;
|
538 |
+
}
|
539 |
+
|
540 |
+
/**
|
541 |
+
* Set all undefined menu fields to the default value
|
542 |
+
*
|
543 |
+
* @param array $item Menu item in the plugin's internal form
|
544 |
+
* @return array
|
545 |
+
*/
|
546 |
+
function apply_defaults($item){
|
547 |
+
foreach($item as $key => $value){
|
548 |
+
//Is the field set?
|
549 |
+
if ($value === null){
|
550 |
+
//Use default, if available
|
551 |
+
if (isset($item['defaults']) && isset($item['defaults'][$key])){
|
552 |
+
$item[$key] = $item['defaults'][$key];
|
553 |
+
}
|
554 |
+
}
|
555 |
+
}
|
556 |
+
return $item;
|
557 |
+
}
|
558 |
+
|
559 |
+
|
560 |
+
/**
|
561 |
+
* Apply custom menu filters to an item of the custom menu.
|
562 |
+
*
|
563 |
+
* Calls two types of filters :
|
564 |
+
* 'custom_admin_$item_type' with the entire $item passed as the argument.
|
565 |
+
* 'custom_admin_$item_type-$field' with the value of a single field of $item as the argument.
|
566 |
+
*
|
567 |
+
* Used when converting the current custom menu to a WP-format menu.
|
568 |
+
*
|
569 |
+
* @param array $item Associative array representing one menu item (either top-level or submenu).
|
570 |
+
* @param string $item_type 'menu' or 'submenu'
|
571 |
+
* @param mixed $extra Optional extra data to pass to hooks.
|
572 |
+
* @return array Filtered menu item.
|
573 |
+
*/
|
574 |
+
function apply_menu_filters($item, $item_type = '', $extra = null){
|
575 |
+
if ( empty($item_type) ){
|
576 |
+
//Only top-level menus have an icon
|
577 |
+
$item_type = isset($item['icon_url'])?'menu':'submenu';
|
578 |
+
}
|
579 |
+
|
580 |
+
$item = apply_filters("custom_admin_{$item_type}", $item, $extra);
|
581 |
+
foreach($item as $field => $value){
|
582 |
+
$item[$field] = apply_filters("custom_admin_{$item_type}-$field", $value, $extra);
|
583 |
+
}
|
584 |
+
|
585 |
+
return $item;
|
586 |
+
}
|
587 |
+
|
588 |
+
/**
|
589 |
+
* Get the value of a menu/submenu field.
|
590 |
+
* Will return the corresponding value from the 'defaults' entry of $item if the
|
591 |
+
* specified field is not set in the item itself.
|
592 |
+
*
|
593 |
+
* @param array $item
|
594 |
+
* @param string $field_name
|
595 |
+
* @param mixed $default Returned if the requested field is not set and is not listed in $item['defaults']. Defaults to null.
|
596 |
+
* @return mixed Field value.
|
597 |
+
*/
|
598 |
+
function get_menu_field($item, $field_name, $default = null){
|
599 |
+
if ( isset($item[$field_name]) && ($item[$field_name] !== null) ){
|
600 |
+
return $item[$field_name];
|
601 |
+
} else {
|
602 |
+
if ( isset($item['defaults']) && isset($item['defaults'][$field_name]) ){
|
603 |
+
return $item['defaults'][$field_name];
|
604 |
+
} else {
|
605 |
+
return $default;
|
606 |
+
}
|
607 |
+
}
|
608 |
+
}
|
609 |
+
|
610 |
+
/**
|
611 |
+
* Custom comparison function that compares menu items based on their position in the menu.
|
612 |
+
*
|
613 |
+
* @param array $a
|
614 |
+
* @param array $b
|
615 |
+
* @return int
|
616 |
+
*/
|
617 |
+
function compare_position($a, $b){
|
618 |
+
if ($a['position']!==null) {
|
619 |
+
$p1 = $a['position'];
|
620 |
+
} else {
|
621 |
+
if ( isset($a['defaults']['position']) ){
|
622 |
+
$p1 = $a['defaults']['position'];
|
623 |
+
} else {
|
624 |
+
$p1 = 0;
|
625 |
+
}
|
626 |
+
}
|
627 |
+
|
628 |
+
if ($b['position']!==null) {
|
629 |
+
$p2 = $b['position'];
|
630 |
+
} else {
|
631 |
+
if ( isset($b['defaults']['position']) ){
|
632 |
+
$p2 = $b['defaults']['position'];
|
633 |
+
} else {
|
634 |
+
$p2 = 0;
|
635 |
+
}
|
636 |
+
}
|
637 |
+
|
638 |
+
return $p1 - $p2;
|
639 |
+
}
|
640 |
+
|
641 |
+
/**
|
642 |
+
* Sort the menus and menu items of a given menu according to their positions
|
643 |
+
*
|
644 |
+
* @param array $tree A menu structure in the internal format
|
645 |
+
* @return array Sorted menu in the internal format
|
646 |
+
*/
|
647 |
+
function sort_menu_tree($tree){
|
648 |
+
//Resort the tree to ensure the found items are in the right spots
|
649 |
+
uasort($tree, array(&$this, 'compare_position'));
|
650 |
+
//Resort all submenus as well
|
651 |
+
foreach ($tree as &$topmenu){
|
652 |
+
if (!empty($topmenu['items'])){
|
653 |
+
uasort($topmenu['items'], array(&$this, 'compare_position'));
|
654 |
+
}
|
655 |
+
}
|
656 |
+
|
657 |
+
return $tree;
|
658 |
+
}
|
659 |
+
|
660 |
+
/**
|
661 |
+
* Convert internal menu representation to the form used by WP.
|
662 |
+
*
|
663 |
+
* Note : While this function doesn't cause any side effects of its own,
|
664 |
+
* it executes several filters that may modify global state. Specifically,
|
665 |
+
* IFrame-handling callbacks in 'extras.php' may insert items into the
|
666 |
+
* global $menu and $submenu arrays.
|
667 |
+
*
|
668 |
+
* @param array $tree
|
669 |
+
* @return array $menu and $submenu
|
670 |
+
*/
|
671 |
+
function tree2wp($tree){
|
672 |
+
$menu = array();
|
673 |
+
$submenu = array();
|
674 |
+
$title_lookup = array();
|
675 |
+
|
676 |
+
//Sort the menu by position
|
677 |
+
uasort($tree, array(&$this, 'compare_position'));
|
678 |
+
|
679 |
+
//Prepare the top menu
|
680 |
+
$first_nonseparator_found = false;
|
681 |
+
foreach ($tree as $topmenu){
|
682 |
+
|
683 |
+
//Skip missing menus, unless they're user-created and thus might point to a non-standard file
|
684 |
+
$custom = $this->get_menu_field($topmenu, 'custom', false);
|
685 |
+
if ( !empty($topmenu['missing']) && !$custom ) {
|
686 |
+
continue;
|
687 |
+
};
|
688 |
+
|
689 |
+
//Skip hidden entries
|
690 |
+
if (!empty($topmenu['hidden'])) continue;
|
691 |
+
|
692 |
+
//Skip leading menu separators. Fixes a superfluous separator showing up
|
693 |
+
//in WP 3.0 (multisite mode) when there's a custom menu and the current user
|
694 |
+
//can't access its first item ("Super Admin").
|
695 |
+
if ( !empty($topmenu['separator']) && !$first_nonseparator_found ) continue;
|
696 |
+
|
697 |
+
$first_nonseparator_found = true;
|
698 |
+
|
699 |
+
//Build the WP item structure, using defaults where necessary
|
700 |
+
$topmenu = $this->apply_defaults($topmenu);
|
701 |
+
$topmenu = $this->apply_menu_filters($topmenu, 'menu');
|
702 |
+
$menu[] = array(
|
703 |
+
$topmenu['menu_title'],
|
704 |
+
$topmenu['access_level'],
|
705 |
+
$topmenu['file'],
|
706 |
+
$topmenu['page_title'],
|
707 |
+
$topmenu['css_class'],
|
708 |
+
$topmenu['hookname'], //ID
|
709 |
+
$topmenu['icon_url']
|
710 |
+
);
|
711 |
+
|
712 |
+
//Prepare the submenu of this menu
|
713 |
+
if( !empty($topmenu['items']) ){
|
714 |
+
$items = $topmenu['items'];
|
715 |
+
//Sort by position
|
716 |
+
uasort($items, array(&$this, 'compare_position'));
|
717 |
+
|
718 |
+
foreach ($items as $item) {
|
719 |
+
|
720 |
+
//Skip missing items, unless they're user-created
|
721 |
+
$custom = $this->get_menu_field($item, 'custom', false);
|
722 |
+
if ( !empty($item['missing']) && !$custom ) continue;
|
723 |
+
//Skip hidden items
|
724 |
+
if (!empty($item['hidden'])) {
|
725 |
+
continue;
|
726 |
+
}
|
727 |
+
|
728 |
+
//Special case : plugin pages that have been moved to a different menu.
|
729 |
+
//If the file field hasn't already been modified, we'll need to adjust it
|
730 |
+
//to point to the old parent. This is required because WP identifies
|
731 |
+
//plugin pages using *both* the plugin file and the parent file.
|
732 |
+
if ( $this->get_menu_field($item, 'is_plugin_page', false) && ($item['file'] === null) ){
|
733 |
+
$default_parent = '';
|
734 |
+
if ( isset($item['defaults']) && isset($item['defaults']['parent'])){
|
735 |
+
$default_parent = $item['defaults']['parent'];
|
736 |
+
}
|
737 |
+
if ( $topmenu['file'] != $default_parent ){
|
738 |
+
$item['file'] = $default_parent . '?page=' . $item['defaults']['file'];
|
739 |
+
}
|
740 |
+
}
|
741 |
+
|
742 |
+
$item = $this->apply_defaults($item);
|
743 |
+
$item = $this->apply_menu_filters($item, 'submenu', $topmenu['file']);
|
744 |
+
$submenu[$topmenu['file']][] = array(
|
745 |
+
$item['menu_title'],
|
746 |
+
$item['access_level'],
|
747 |
+
$item['file'],
|
748 |
+
$item['page_title'],
|
749 |
+
);
|
750 |
+
|
751 |
+
//Make a note of the page's correct title so we can fix it later
|
752 |
+
//if necessary.
|
753 |
+
$title_lookup[$item['file']] = $item['menu_title'];
|
754 |
+
}
|
755 |
+
}
|
756 |
+
}
|
757 |
+
|
758 |
+
return array($menu, $submenu, $title_lookup);
|
759 |
+
}
|
760 |
+
|
761 |
+
/**
|
762 |
+
* Upgrade a menu tree to the currently used structure
|
763 |
+
* Does nothing if the menu is already up to date.
|
764 |
+
*
|
765 |
+
* @param array $tree
|
766 |
+
* @return array
|
767 |
+
*/
|
768 |
+
function upgrade_menu_structure($tree){
|
769 |
+
|
770 |
+
//Append new fields, if any
|
771 |
+
foreach($tree as &$menu){
|
772 |
+
$menu = array_merge($this->templates['blank_menu'], $menu);
|
773 |
+
$menu['defaults'] = array_merge($this->templates['basic_defaults'], $menu['defaults']);
|
774 |
+
|
775 |
+
foreach($menu['items'] as $item_file => $item){
|
776 |
+
$item = array_merge($this->templates['blank_item'], $item);
|
777 |
+
$item['defaults'] = array_merge($this->templates['basic_defaults'], $item['defaults']);
|
778 |
+
$menu['items'][$item_file] = $item;
|
779 |
+
}
|
780 |
+
}
|
781 |
+
|
782 |
+
return $tree;
|
783 |
+
}
|
784 |
+
|
785 |
+
/**
|
786 |
+
* Output the menu editor page
|
787 |
+
*
|
788 |
+
* @return void
|
789 |
+
*/
|
790 |
+
function page_menu_editor(){
|
791 |
+
global $menu, $submenu;
|
792 |
+
global $wp_roles;
|
793 |
+
|
794 |
+
if ( !$this->current_user_can_edit_menu() ){
|
795 |
+
die("Access denied");
|
796 |
+
}
|
797 |
+
|
798 |
+
$action = isset($_POST['action'])?$_POST['action']:(isset($_GET['action'])?$_GET['action']:'');
|
799 |
+
do_action('admin_menu_editor_header', $action);
|
800 |
+
|
801 |
+
//Handle form submissions
|
802 |
+
if (isset($_POST['data'])){
|
803 |
+
check_admin_referer('menu-editor-form');
|
804 |
+
|
805 |
+
//Try to decode a menu tree encoded as JSON
|
806 |
+
$data = $this->json_decode($_POST['data'], true);
|
807 |
+
if (!$data || count(($data) < 2) ){
|
808 |
+
$fixed = stripslashes($_POST['data']);
|
809 |
+
$data = $this->json_decode( $fixed, true );
|
810 |
+
}
|
811 |
+
|
812 |
+
$url = remove_query_arg('noheader');
|
813 |
+
|
814 |
+
if ($data){
|
815 |
+
//Save the custom menu
|
816 |
+
$this->options['custom_menu'] = $data;
|
817 |
+
$this->save_options();
|
818 |
+
//Redirect back to the editor and display the success message
|
819 |
+
wp_redirect( add_query_arg('message', 1, $url) );
|
820 |
+
} else {
|
821 |
+
//Or redirect & display the error message
|
822 |
+
wp_redirect( add_query_arg('message', 2, $url) );
|
823 |
+
}
|
824 |
+
die();
|
825 |
+
}
|
826 |
+
|
827 |
+
//Attach a "Feedback" link to the screen meta panel.
|
828 |
+
$this->print_uservoice_widget();
|
829 |
+
//Kindly remind the user to give me money
|
830 |
+
if ( !apply_filters('admin_menu_editor_is_pro', false) ){
|
831 |
+
$this->print_upgrade_notice();
|
832 |
+
}
|
833 |
+
?>
|
834 |
+
<div class="wrap">
|
835 |
+
<h2>
|
836 |
+
<?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
|
837 |
+
</h2>
|
838 |
+
|
839 |
+
<?php
|
840 |
+
|
841 |
+
if ( !empty($_GET['message']) ){
|
842 |
+
if ( intval($_GET['message']) == 1 ){
|
843 |
+
echo '<div id="message" class="updated fade"><p><strong>Settings saved.</strong></p></div>';
|
844 |
+
} elseif ( intval($_GET['message']) == 2 ) {
|
845 |
+
echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
|
846 |
+
}
|
847 |
+
}
|
848 |
+
|
849 |
+
//Build a tree struct. for the default menu
|
850 |
+
$default_menu = $this->wp2tree($this->default_wp_menu, $this->default_wp_submenu);
|
851 |
+
|
852 |
+
//Is there a custom menu?
|
853 |
+
if (!empty($this->custom_menu)){
|
854 |
+
$custom_menu = $this->custom_menu;
|
855 |
+
} else {
|
856 |
+
//Start out with the default menu if there is no user-created one
|
857 |
+
$custom_menu = $default_menu;
|
858 |
+
}
|
859 |
+
|
860 |
+
//Encode both menus as JSON
|
861 |
+
$default_menu_js = $this->getMenuAsJS($default_menu);
|
862 |
+
$custom_menu_js = $this->getMenuAsJS($custom_menu);
|
863 |
+
|
864 |
+
$plugin_url = $this->plugin_dir_url;
|
865 |
+
$images_url = $this->plugin_dir_url . '/images';
|
866 |
+
|
867 |
+
//Create a list of all known capabilities and roles. Used for the dropdown list on the access field.
|
868 |
+
$all_capabilities = $this->get_all_capabilities();
|
869 |
+
$all_capabilities = array_keys($all_capabilities);
|
870 |
+
natcasesort($all_capabilities);
|
871 |
+
|
872 |
+
$all_roles = $this->get_all_roles();
|
873 |
+
asort($all_roles);
|
874 |
+
?>
|
875 |
+
<div id='ws_menu_editor'>
|
876 |
+
<div class='ws_main_container'>
|
877 |
+
<div class='ws_toolbar'>
|
878 |
+
<div class="ws_button_container">
|
879 |
+
<a id='ws_cut_menu' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' /></a>
|
880 |
+
<a id='ws_copy_menu' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $images_url; ?>/page_white_copy.png' /></a>
|
881 |
+
<a id='ws_paste_menu' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $images_url; ?>/page_white_paste.png' /></a>
|
882 |
+
|
883 |
+
<div class="ws_separator"> </div>
|
884 |
+
|
885 |
+
<a id='ws_new_menu' class='ws_button' href='javascript:void(0)' title='New menu'><img src='<?php echo $images_url; ?>/page_white_add.png' /></a>
|
886 |
+
<a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $images_url; ?>/plugin_disabled.png' /></a>
|
887 |
+
<a id='ws_delete_menu' class='ws_button' href='javascript:void(0)' title='Delete menu'><img src='<?php echo $images_url; ?>/page_white_delete.png' /></a>
|
888 |
+
|
889 |
+
<div class="ws_separator"> </div>
|
890 |
+
|
891 |
+
<a id='ws_new_separator' class='ws_button' href='javascript:void(0)' title='New separator'><img src='<?php echo $images_url; ?>/separator_add.png' /></a>
|
892 |
+
</div>
|
893 |
+
</div>
|
894 |
+
|
895 |
+
<div id='ws_menu_box' class="ws_box">
|
896 |
+
</div>
|
897 |
+
</div>
|
898 |
+
<div class='ws_main_container'>
|
899 |
+
<div class='ws_toolbar'>
|
900 |
+
<div class="ws_button_container">
|
901 |
+
<a id='ws_cut_item' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' /></a>
|
902 |
+
<a id='ws_copy_item' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $images_url; ?>/page_white_copy.png' /></a>
|
903 |
+
<a id='ws_paste_item' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $images_url; ?>/page_white_paste.png' /></a>
|
904 |
+
|
905 |
+
<div class="ws_separator"> </div>
|
906 |
+
|
907 |
+
<a id='ws_new_item' class='ws_button' href='javascript:void(0)' title='New menu item'><img src='<?php echo $images_url; ?>/page_white_add.png' /></a>
|
908 |
+
<a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $images_url; ?>/plugin_disabled.png' /></a>
|
909 |
+
<a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $images_url; ?>/page_white_delete.png' /></a>
|
910 |
+
|
911 |
+
<div class="ws_separator"> </div>
|
912 |
+
|
913 |
+
<a id='ws_sort_ascending' class='ws_button' href='javascript:void(0)' title='Sort ascending'>
|
914 |
+
<img src='<?php echo $images_url; ?>/sort_ascending.png' />
|
915 |
+
</a>
|
916 |
+
<a id='ws_sort_descending' class='ws_button' href='javascript:void(0)' title='Sort descending'>
|
917 |
+
<img src='<?php echo $images_url; ?>/sort_descending.png' />
|
918 |
+
</a>
|
919 |
+
</div>
|
920 |
+
</div>
|
921 |
+
|
922 |
+
<div id='ws_submenu_box' class="ws_box">
|
923 |
+
</div>
|
924 |
+
</div>
|
925 |
+
</div>
|
926 |
+
|
927 |
+
<div class="ws_main_container" id="ws_editor_sidebar">
|
928 |
+
<form method="post" action="<?php echo admin_url('options-general.php?page=menu_editor&noheader=1'); ?>" id='ws_main_form' name='ws_main_form'>
|
929 |
+
<?php wp_nonce_field('menu-editor-form'); ?>
|
930 |
+
<input type="hidden" name="data" id="ws_data" value="">
|
931 |
+
<input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
|
932 |
+
</form>
|
933 |
+
|
934 |
+
<input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
|
935 |
+
<input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
|
936 |
+
|
937 |
+
<?php
|
938 |
+
do_action('admin_menu_editor_sidebar');
|
939 |
+
?>
|
940 |
+
</div>
|
941 |
+
|
942 |
+
</div>
|
943 |
+
|
944 |
+
<?php
|
945 |
+
//Createa a pop-up capability selector
|
946 |
+
$capSelector = array('<select id="ws_cap_selector" size="10">');
|
947 |
+
|
948 |
+
$capSelector[] = '<optgroup label="Roles">';
|
949 |
+
foreach($all_roles as $role_id => $role_name){
|
950 |
+
$capSelector[] = sprintf(
|
951 |
+
'<option value="%s">%s</option>',
|
952 |
+
esc_attr($role_id),
|
953 |
+
$role_name
|
954 |
+
);
|
955 |
+
}
|
956 |
+
$capSelector[] = '</optgroup>';
|
957 |
+
|
958 |
+
$capSelector[] = '<optgroup label="Capabilities">';
|
959 |
+
foreach($all_capabilities as $cap){
|
960 |
+
$capSelector[] = sprintf(
|
961 |
+
'<option value="%s">%s</option>',
|
962 |
+
esc_attr($cap),
|
963 |
+
$cap
|
964 |
+
);
|
965 |
+
}
|
966 |
+
$capSelector[] = '</optgroup>';
|
967 |
+
$capSelector[] = '</select>';
|
968 |
+
|
969 |
+
echo implode("\n", $capSelector);
|
970 |
+
?>
|
971 |
+
|
972 |
+
<span id="ws-ame-screen-meta-contents" style="display:none;">
|
973 |
+
<label for="ws-hide-advanced-settings">
|
974 |
+
<input type="checkbox" id="ws-hide-advanced-settings"<?php
|
975 |
+
if ( $this->options['hide_advanced_settings'] ){
|
976 |
+
echo ' checked="checked"';
|
977 |
+
}
|
978 |
+
?> /> Hide advanced options
|
979 |
+
</label>
|
980 |
+
</span>
|
981 |
+
|
982 |
+
<script type='text/javascript'>
|
983 |
+
|
984 |
+
var defaultMenu = <?php echo $default_menu_js; ?>;
|
985 |
+
var customMenu = <?php echo $custom_menu_js; ?>;
|
986 |
+
|
987 |
+
var imagesUrl = "<?php echo esc_js($images_url); ?>";
|
988 |
+
|
989 |
+
var adminAjaxUrl = "<?php echo esc_js(admin_url('admin-ajax.php')); ?>";
|
990 |
+
|
991 |
+
var hideAdvancedSettings = <?php echo $this->options['hide_advanced_settings']?'true':'false'; ?>;
|
992 |
+
var hideAdvancedSettingsNonce = '<?php echo esc_js(wp_create_nonce('ws_ame_save_screen_options')); ?>';
|
993 |
+
|
994 |
+
var captionShowAdvanced = 'Show advanced options';
|
995 |
+
var captionHideAdvanced = 'Hide advanced options';
|
996 |
+
|
997 |
+
window.wsMenuEditorPro = false; //Will be overwritten if extras are loaded
|
998 |
+
|
999 |
+
</script>
|
1000 |
+
|
1001 |
+
<?php
|
1002 |
+
|
1003 |
+
//Let the Pro version script output it's extra HTML & scripts.
|
1004 |
+
do_action('admin_menu_editor_footer');
|
1005 |
+
}
|
1006 |
+
|
1007 |
+
/**
|
1008 |
+
* Retrieve a list of all known capabilities of all roles
|
1009 |
+
*
|
1010 |
+
* @return array Associative array with capability names as keys
|
1011 |
+
*/
|
1012 |
+
function get_all_capabilities(){
|
1013 |
+
global $wp_roles;
|
1014 |
+
|
1015 |
+
$capabilities = array();
|
1016 |
+
|
1017 |
+
if ( !isset($wp_roles) || !isset($wp_roles->roles) ){
|
1018 |
+
return $capabilities;
|
1019 |
+
}
|
1020 |
+
|
1021 |
+
//Iterate over all known roles and collect their capabilities
|
1022 |
+
foreach($wp_roles->roles as $role_id => $role){
|
1023 |
+
if ( !empty($role['capabilities']) && is_array($role['capabilities']) ){ //Being defensive here
|
1024 |
+
$capabilities = array_merge($capabilities, $role['capabilities']);
|
1025 |
+
}
|
1026 |
+
}
|
1027 |
+
|
1028 |
+
//Add multisite-specific capabilities (not listed in any roles in WP 3.0)
|
1029 |
+
$multisite_caps = array(
|
1030 |
+
'manage_sites' => 1,
|
1031 |
+
'manage_network' => 1,
|
1032 |
+
'manage_network_users' => 1,
|
1033 |
+
'manage_network_themes' => 1,
|
1034 |
+
'manage_network_options' => 1,
|
1035 |
+
'manage_network_plugins' => 1,
|
1036 |
+
);
|
1037 |
+
$capabilities = array_merge($capabilities, $multisite_caps);
|
1038 |
+
|
1039 |
+
return $capabilities;
|
1040 |
+
}
|
1041 |
+
|
1042 |
+
/**
|
1043 |
+
* Retrieve a list of all known roles
|
1044 |
+
*
|
1045 |
+
* @return array Associative array with role IDs as keys and role display names as values
|
1046 |
+
*/
|
1047 |
+
function get_all_roles(){
|
1048 |
+
global $wp_roles;
|
1049 |
+
$roles = array();
|
1050 |
+
|
1051 |
+
if ( !isset($wp_roles) || !isset($wp_roles->roles) ){
|
1052 |
+
return $roles;
|
1053 |
+
}
|
1054 |
+
|
1055 |
+
foreach($wp_roles->roles as $role_id => $role){
|
1056 |
+
$roles[$role_id] = $role['name'];
|
1057 |
+
}
|
1058 |
+
|
1059 |
+
return $roles;
|
1060 |
+
}
|
1061 |
+
|
1062 |
+
/**
|
1063 |
+
* Output the JavaScript that adds the "Feedback" widget to screen meta.
|
1064 |
+
*
|
1065 |
+
* @return void
|
1066 |
+
*/
|
1067 |
+
function print_uservoice_widget(){
|
1068 |
+
?>
|
1069 |
+
<script type="text/javascript">
|
1070 |
+
(function($){
|
1071 |
+
$('#screen-meta-links').append(
|
1072 |
+
'<div id="ws-ame-feedback-widget-wrap" class="hide-if-no-js screen-meta-toggle">' +
|
1073 |
+
'<a href="http://feedback.w-shadow.com/forums/58572-admin-menu-editor" id="ws-ame-feedback-widget" class="show-settings" target="_blank" title="Open the user feedback forum">Feedback</a>' +
|
1074 |
+
'</div>'
|
1075 |
+
);
|
1076 |
+
})(jQuery);
|
1077 |
+
</script>
|
1078 |
+
<?php
|
1079 |
+
}
|
1080 |
+
|
1081 |
+
/**
|
1082 |
+
* Output the "Upgrade to Pro" message
|
1083 |
+
*
|
1084 |
+
* @return void
|
1085 |
+
*/
|
1086 |
+
function print_upgrade_notice(){
|
1087 |
+
?>
|
1088 |
+
<script type="text/javascript">
|
1089 |
+
(function($){
|
1090 |
+
$('#screen-meta-links').append(
|
1091 |
+
'<div id="ws-pro-version-notice" class="hide-if-no-js screen-meta-toggle">' +
|
1092 |
+
'<a href="http://wpplugins.com/plugin/146/admin-menu-editor-pro" id="ws-pro-version-notice-link" class="show-settings" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
|
1093 |
+
'</div>'
|
1094 |
+
);
|
1095 |
+
})(jQuery);
|
1096 |
+
</script>
|
1097 |
+
<?php
|
1098 |
+
}
|
1099 |
+
|
1100 |
+
/**
|
1101 |
+
* AJAX callback for saving screen options (whether to show or to hide advanced menu options).
|
1102 |
+
*
|
1103 |
+
* Handles the 'ws_ame_save_screen_options' action. The new option value
|
1104 |
+
* is read from $_POST['hide_advanced_settings'].
|
1105 |
+
*
|
1106 |
+
* @return void
|
1107 |
+
*/
|
1108 |
+
function ajax_save_screen_options(){
|
1109 |
+
if (!current_user_can('manage_options') || !check_ajax_referer('ws_ame_save_screen_options', false, false)){
|
1110 |
+
die( $this->json_encode( array(
|
1111 |
+
'error' => "You're not allowed to do that!"
|
1112 |
+
)));
|
1113 |
+
}
|
1114 |
+
|
1115 |
+
$this->options['hide_advanced_settings'] = !empty($_POST['hide_advanced_settings']);
|
1116 |
+
$this->save_options();
|
1117 |
+
die('1');
|
1118 |
+
}
|
1119 |
+
|
1120 |
+
/**
|
1121 |
+
* A callback for the stub meta box added to the plugin's page. Does nothing.
|
1122 |
+
*
|
1123 |
+
* @return void
|
1124 |
+
*/
|
1125 |
+
function noop(){
|
1126 |
+
//nihil
|
1127 |
+
}
|
1128 |
+
|
1129 |
+
} //class
|
1130 |
+
|
1131 |
+
endif;
|
1132 |
+
|
1133 |
+
?>
|
shadow_plugin_framework.php → includes/shadow_plugin_framework.php
RENAMED
@@ -14,9 +14,15 @@ if ( ! defined( 'WP_PLUGIN_URL' ) )
|
|
14 |
define( 'WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins' );
|
15 |
if ( ! defined( 'WP_PLUGIN_DIR' ) )
|
16 |
define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
class MenuEd_ShadowPluginFramework {
|
19 |
-
public static $framework_version = '0.
|
20 |
|
21 |
public $is_mu_plugin = null; //True if installed in the mu-plugins directory, false otherwise
|
22 |
|
@@ -24,6 +30,7 @@ class MenuEd_ShadowPluginFramework {
|
|
24 |
public $option_name = ''; //should be set or overriden by the plugin
|
25 |
protected $defaults = array(); //should be set or overriden by the plugin
|
26 |
protected $sitewide_options = false; //WPMU only : save the setting in a site-wide option
|
|
|
27 |
|
28 |
public $plugin_file = ''; //Filename of the plugin.
|
29 |
public $plugin_basename = ''; //Basename of the plugin, as returned by plugin_basename().
|
@@ -35,17 +42,18 @@ class MenuEd_ShadowPluginFramework {
|
|
35 |
protected $settings_link = ''; //If set, this will be automatically added after "Deactivate"/"Edit".
|
36 |
|
37 |
/**
|
38 |
-
*
|
39 |
-
*
|
40 |
*
|
41 |
* @param string $plugin_file Plugin's filename. Usuallly you can just use __FILE__.
|
42 |
* @return void
|
43 |
*/
|
44 |
-
|
45 |
if ($plugin_file == ''){
|
46 |
//Try to guess the name of the file that included this file.
|
47 |
//Not implemented yet.
|
48 |
}
|
|
|
49 |
|
50 |
if ( is_null($this->is_mu_plugin) )
|
51 |
$this->is_mu_plugin = $this->is_in_wpmu_plugin_dir($plugin_file);
|
@@ -59,6 +67,32 @@ class MenuEd_ShadowPluginFramework {
|
|
59 |
$this->plugin_dir_url = WP_PLUGIN_URL . '/' . dirname($this->plugin_basename);
|
60 |
}
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
/************************************
|
63 |
Load settings
|
64 |
************************************/
|
@@ -73,12 +107,7 @@ class MenuEd_ShadowPluginFramework {
|
|
73 |
$this->load_options();
|
74 |
}
|
75 |
|
76 |
-
|
77 |
-
Add the default hooks
|
78 |
-
************************************/
|
79 |
-
add_action('activate_'.$this->plugin_basename, array(&$this,'activate'));
|
80 |
-
add_action('deactivate_'.$this->plugin_basename, array(&$this,'deactivate'));
|
81 |
-
|
82 |
if ($this->settings_link)
|
83 |
add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
|
84 |
|
@@ -87,16 +116,25 @@ class MenuEd_ShadowPluginFramework {
|
|
87 |
}
|
88 |
|
89 |
/**
|
90 |
-
*
|
91 |
-
* Loads the
|
92 |
*
|
|
|
93 |
* @return boolean TRUE if options were loaded okay and FALSE otherwise.
|
94 |
*/
|
95 |
-
function load_options(){
|
|
|
|
|
|
|
|
|
96 |
if ( $this->sitewide_options ) {
|
97 |
-
$this->options = get_site_option($
|
98 |
} else {
|
99 |
-
$this->options = get_option($
|
|
|
|
|
|
|
|
|
100 |
}
|
101 |
|
102 |
if(!is_array($this->options)){
|
@@ -108,6 +146,57 @@ class MenuEd_ShadowPluginFramework {
|
|
108 |
}
|
109 |
}
|
110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
/**
|
112 |
* ShadowPluginFramework::set_magic_hooks()
|
113 |
* Automagically sets up hooks for all methods named "hook_[tag]". Uses the Reflection API.
|
@@ -132,22 +221,7 @@ class MenuEd_ShadowPluginFramework {
|
|
132 |
unset($class);
|
133 |
}
|
134 |
|
135 |
-
|
136 |
-
* ShadowPluginFramework::save_options()
|
137 |
-
* Saves the $options array to the database.
|
138 |
-
*
|
139 |
-
* @return void
|
140 |
-
*/
|
141 |
-
function save_options(){
|
142 |
-
if ($this->option_name) {
|
143 |
-
if ( $this->sitewide_options ) {
|
144 |
-
update_site_option($this->option_name, $this->options);
|
145 |
-
} else {
|
146 |
-
update_option($this->option_name, $this->options);
|
147 |
-
}
|
148 |
-
}
|
149 |
-
}
|
150 |
-
|
151 |
/**
|
152 |
* ShadowPluginFramework::activate()
|
153 |
* Stub function for the activation hook. Simply stores the default configuration.
|
@@ -194,7 +268,6 @@ class MenuEd_ShadowPluginFramework {
|
|
194 |
}
|
195 |
|
196 |
/**
|
197 |
-
* MenuEd_ShadowPluginFramework::is_in_wpmu_plugin_dir()
|
198 |
* Checks if the specified file is inside the mu-plugins directory.
|
199 |
*
|
200 |
* @param string $filename The filename to check. Leave blank to use the current plugin's filename.
|
@@ -210,6 +283,52 @@ class MenuEd_ShadowPluginFramework {
|
|
210 |
return (strpos( realpath($filename), realpath(WPMU_PLUGIN_DIR) ) !== false);
|
211 |
}
|
212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
}
|
214 |
|
215 |
?>
|
14 |
define( 'WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins' );
|
15 |
if ( ! defined( 'WP_PLUGIN_DIR' ) )
|
16 |
define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
|
17 |
+
|
18 |
+
|
19 |
+
//Load JSON functions for PHP < 5.2
|
20 |
+
if (!class_exists('Services_JSON')){
|
21 |
+
require ABSPATH . WPINC . '/class-json.php';
|
22 |
+
}
|
23 |
|
24 |
class MenuEd_ShadowPluginFramework {
|
25 |
+
public static $framework_version = '0.4';
|
26 |
|
27 |
public $is_mu_plugin = null; //True if installed in the mu-plugins directory, false otherwise
|
28 |
|
30 |
public $option_name = ''; //should be set or overriden by the plugin
|
31 |
protected $defaults = array(); //should be set or overriden by the plugin
|
32 |
protected $sitewide_options = false; //WPMU only : save the setting in a site-wide option
|
33 |
+
protected $serialize_with_json = false; //Use the JSON format for option storage
|
34 |
|
35 |
public $plugin_file = ''; //Filename of the plugin.
|
36 |
public $plugin_basename = ''; //Basename of the plugin, as returned by plugin_basename().
|
42 |
protected $settings_link = ''; //If set, this will be automatically added after "Deactivate"/"Edit".
|
43 |
|
44 |
/**
|
45 |
+
* Class constructor. Populates some internal fields, then calls the plugin's own
|
46 |
+
* intializer (if any).
|
47 |
*
|
48 |
* @param string $plugin_file Plugin's filename. Usuallly you can just use __FILE__.
|
49 |
* @return void
|
50 |
*/
|
51 |
+
function __construct( $plugin_file = '', $option_name = null ){
|
52 |
if ($plugin_file == ''){
|
53 |
//Try to guess the name of the file that included this file.
|
54 |
//Not implemented yet.
|
55 |
}
|
56 |
+
$this->option_name = $option_name;
|
57 |
|
58 |
if ( is_null($this->is_mu_plugin) )
|
59 |
$this->is_mu_plugin = $this->is_in_wpmu_plugin_dir($plugin_file);
|
67 |
$this->plugin_dir_url = WP_PLUGIN_URL . '/' . dirname($this->plugin_basename);
|
68 |
}
|
69 |
|
70 |
+
/************************************
|
71 |
+
Add the default hooks
|
72 |
+
************************************/
|
73 |
+
add_action('activate_'.$this->plugin_basename, array(&$this,'activate'));
|
74 |
+
add_action('deactivate_'.$this->plugin_basename, array(&$this,'deactivate'));
|
75 |
+
|
76 |
+
$this->init(); //Call the plugin's init() function
|
77 |
+
$this->init_finish(); //Complete initialization by loading settings, etc
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Init the plugin. Should be overridden in a sub-class.
|
82 |
+
* Called by the class constructor.
|
83 |
+
*
|
84 |
+
* @return void
|
85 |
+
*/
|
86 |
+
function init(){
|
87 |
+
//Do nothing.
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Initialize settings and set up magic hooks.
|
92 |
+
*
|
93 |
+
* @return void
|
94 |
+
*/
|
95 |
+
function init_finish(){
|
96 |
/************************************
|
97 |
Load settings
|
98 |
************************************/
|
107 |
$this->load_options();
|
108 |
}
|
109 |
|
110 |
+
//Add a "Settings" action link
|
|
|
|
|
|
|
|
|
|
|
111 |
if ($this->settings_link)
|
112 |
add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
|
113 |
|
116 |
}
|
117 |
|
118 |
/**
|
119 |
+
* Load the plugin's configuration.
|
120 |
+
* Loads the specified option into $this->options, substituting defaults where necessary.
|
121 |
*
|
122 |
+
* @param string $option_name Optional. The slug of the option to load. If not set, the value of $this->option_name will be used instead.
|
123 |
* @return boolean TRUE if options were loaded okay and FALSE otherwise.
|
124 |
*/
|
125 |
+
function load_options($option_name = null){
|
126 |
+
if ( empty($option_name) ){
|
127 |
+
$option_name = $this->option_name;
|
128 |
+
}
|
129 |
+
|
130 |
if ( $this->sitewide_options ) {
|
131 |
+
$this->options = get_site_option($option_name);
|
132 |
} else {
|
133 |
+
$this->options = get_option($option_name);
|
134 |
+
}
|
135 |
+
|
136 |
+
if ( $this->serialize_with_json || is_string($this->options) ){
|
137 |
+
$this->options = $this->json_decode($this->options, true);
|
138 |
}
|
139 |
|
140 |
if(!is_array($this->options)){
|
146 |
}
|
147 |
}
|
148 |
|
149 |
+
/**
|
150 |
+
* ShadowPluginFramework::save_options()
|
151 |
+
* Saves the $options array to the database.
|
152 |
+
*
|
153 |
+
* @return void
|
154 |
+
*/
|
155 |
+
function save_options(){
|
156 |
+
if ($this->option_name) {
|
157 |
+
$stored_options = $this->options;
|
158 |
+
if ( $this->serialize_with_json ){
|
159 |
+
$stored_options = $this->json_encode($stored_options);
|
160 |
+
}
|
161 |
+
|
162 |
+
if ( $this->sitewide_options ) {
|
163 |
+
update_site_option($this->option_name, $stored_options);
|
164 |
+
} else {
|
165 |
+
update_option($this->option_name, $stored_options);
|
166 |
+
}
|
167 |
+
}
|
168 |
+
}
|
169 |
+
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Backwards fompatible json_decode.
|
173 |
+
*
|
174 |
+
* @param string $data
|
175 |
+
* @param bool $assoc Decode objects as associative arrays.
|
176 |
+
* @return string
|
177 |
+
*/
|
178 |
+
function json_decode($data, $assoc=false){
|
179 |
+
if ( function_exists('json_decode') ){
|
180 |
+
return json_decode($data, $assoc);
|
181 |
+
}
|
182 |
+
$flag = $assoc?SERVICES_JSON_LOOSE_TYPE:0;
|
183 |
+
$json = new Services_JSON($flag);
|
184 |
+
return( $json->decode($data) );
|
185 |
+
}
|
186 |
+
|
187 |
+
/**
|
188 |
+
* Backwards fompatible json_encode.
|
189 |
+
*
|
190 |
+
* @param mixed $data
|
191 |
+
* @return string
|
192 |
+
*/
|
193 |
+
function json_encode($data) {
|
194 |
+
$json = new Services_JSON();
|
195 |
+
return( $json->encodeUnsafe($data) );
|
196 |
+
}
|
197 |
+
|
198 |
+
|
199 |
+
|
200 |
/**
|
201 |
* ShadowPluginFramework::set_magic_hooks()
|
202 |
* Automagically sets up hooks for all methods named "hook_[tag]". Uses the Reflection API.
|
221 |
unset($class);
|
222 |
}
|
223 |
|
224 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
/**
|
226 |
* ShadowPluginFramework::activate()
|
227 |
* Stub function for the activation hook. Simply stores the default configuration.
|
268 |
}
|
269 |
|
270 |
/**
|
|
|
271 |
* Checks if the specified file is inside the mu-plugins directory.
|
272 |
*
|
273 |
* @param string $filename The filename to check. Leave blank to use the current plugin's filename.
|
283 |
return (strpos( realpath($filename), realpath(WPMU_PLUGIN_DIR) ) !== false);
|
284 |
}
|
285 |
|
286 |
+
/**
|
287 |
+
* Check if the plugin is active for the entire network.
|
288 |
+
* Will return true when the plugin is installed in /mu-plugins/ (WPMU, pre-3.0)
|
289 |
+
* or has been activated via "Network Activate" (WP 3.0+).
|
290 |
+
*
|
291 |
+
* Blame the ridiculous blog/site/network confusion perpetrated by
|
292 |
+
* the WP API for the silly name.
|
293 |
+
*
|
294 |
+
* @return bool
|
295 |
+
*/
|
296 |
+
function is_super_plugin(){
|
297 |
+
if ( is_null($this->is_mu_plugin) ){
|
298 |
+
$this->is_mu_plugin = $this->is_in_wpmu_plugin_dir($this->plugin_file);
|
299 |
+
}
|
300 |
+
|
301 |
+
if ( $this->is_mu_plugin ){
|
302 |
+
return true;
|
303 |
+
} else {
|
304 |
+
return $this->is_plugin_active_for_network($this->plugin_basename);
|
305 |
+
}
|
306 |
+
}
|
307 |
+
|
308 |
+
/**
|
309 |
+
* Check whether the plugin is active for the entire network.
|
310 |
+
*
|
311 |
+
* Silly WP doesn't load the file that contains this native function until *after*
|
312 |
+
* all plugins are loaded, so until then we use a copy-pasted version of the same.
|
313 |
+
*
|
314 |
+
* @param string $plugin
|
315 |
+
* @return bool
|
316 |
+
*/
|
317 |
+
function is_plugin_active_for_network( $plugin ) {
|
318 |
+
if ( function_exists('is_plugin_active_for_network') ){
|
319 |
+
return is_plugin_active_for_network($plugin);
|
320 |
+
}
|
321 |
+
|
322 |
+
if ( !is_multisite() )
|
323 |
+
return false;
|
324 |
+
|
325 |
+
$plugins = get_site_option( 'active_sitewide_plugins');
|
326 |
+
if ( isset($plugins[$plugin]) )
|
327 |
+
return true;
|
328 |
+
|
329 |
+
return false;
|
330 |
+
}
|
331 |
+
|
332 |
}
|
333 |
|
334 |
?>
|
js/jquery.form.js
ADDED
@@ -0,0 +1,675 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*!
|
2 |
+
* jQuery Form Plugin
|
3 |
+
* version: 2.43 (12-MAR-2010)
|
4 |
+
* @requires jQuery v1.3.2 or later
|
5 |
+
*
|
6 |
+
* Examples and documentation at: http://malsup.com/jquery/form/
|
7 |
+
* Dual licensed under the MIT and GPL licenses:
|
8 |
+
* http://www.opensource.org/licenses/mit-license.php
|
9 |
+
* http://www.gnu.org/licenses/gpl.html
|
10 |
+
*/
|
11 |
+
;(function($) {
|
12 |
+
|
13 |
+
/*
|
14 |
+
Usage Note:
|
15 |
+
-----------
|
16 |
+
Do not use both ajaxSubmit and ajaxForm on the same form. These
|
17 |
+
functions are intended to be exclusive. Use ajaxSubmit if you want
|
18 |
+
to bind your own submit handler to the form. For example,
|
19 |
+
|
20 |
+
$(document).ready(function() {
|
21 |
+
$('#myForm').bind('submit', function() {
|
22 |
+
$(this).ajaxSubmit({
|
23 |
+
target: '#output'
|
24 |
+
});
|
25 |
+
return false; // <-- important!
|
26 |
+
});
|
27 |
+
});
|
28 |
+
|
29 |
+
Use ajaxForm when you want the plugin to manage all the event binding
|
30 |
+
for you. For example,
|
31 |
+
|
32 |
+
$(document).ready(function() {
|
33 |
+
$('#myForm').ajaxForm({
|
34 |
+
target: '#output'
|
35 |
+
});
|
36 |
+
});
|
37 |
+
|
38 |
+
When using ajaxForm, the ajaxSubmit function will be invoked for you
|
39 |
+
at the appropriate time.
|
40 |
+
*/
|
41 |
+
|
42 |
+
/**
|
43 |
+
* ajaxSubmit() provides a mechanism for immediately submitting
|
44 |
+
* an HTML form using AJAX.
|
45 |
+
*/
|
46 |
+
$.fn.ajaxSubmit = function(options) {
|
47 |
+
// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
|
48 |
+
if (!this.length) {
|
49 |
+
log('ajaxSubmit: skipping submit process - no element selected');
|
50 |
+
return this;
|
51 |
+
}
|
52 |
+
|
53 |
+
if (typeof options == 'function')
|
54 |
+
options = { success: options };
|
55 |
+
|
56 |
+
var url = $.trim(this.attr('action'));
|
57 |
+
if (url) {
|
58 |
+
// clean url (don't include hash vaue)
|
59 |
+
url = (url.match(/^([^#]+)/)||[])[1];
|
60 |
+
}
|
61 |
+
url = url || window.location.href || '';
|
62 |
+
|
63 |
+
options = $.extend({
|
64 |
+
url: url,
|
65 |
+
type: this.attr('method') || 'GET',
|
66 |
+
iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
|
67 |
+
}, options || {});
|
68 |
+
|
69 |
+
// hook for manipulating the form data before it is extracted;
|
70 |
+
// convenient for use with rich editors like tinyMCE or FCKEditor
|
71 |
+
var veto = {};
|
72 |
+
this.trigger('form-pre-serialize', [this, options, veto]);
|
73 |
+
if (veto.veto) {
|
74 |
+
log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
|
75 |
+
return this;
|
76 |
+
}
|
77 |
+
|
78 |
+
// provide opportunity to alter form data before it is serialized
|
79 |
+
if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
|
80 |
+
log('ajaxSubmit: submit aborted via beforeSerialize callback');
|
81 |
+
return this;
|
82 |
+
}
|
83 |
+
|
84 |
+
var a = this.formToArray(options.semantic);
|
85 |
+
if (options.data) {
|
86 |
+
options.extraData = options.data;
|
87 |
+
for (var n in options.data) {
|
88 |
+
if(options.data[n] instanceof Array) {
|
89 |
+
for (var k in options.data[n])
|
90 |
+
a.push( { name: n, value: options.data[n][k] } );
|
91 |
+
}
|
92 |
+
else
|
93 |
+
a.push( { name: n, value: options.data[n] } );
|
94 |
+
}
|
95 |
+
}
|
96 |
+
|
97 |
+
// give pre-submit callback an opportunity to abort the submit
|
98 |
+
if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
|
99 |
+
log('ajaxSubmit: submit aborted via beforeSubmit callback');
|
100 |
+
return this;
|
101 |
+
}
|
102 |
+
|
103 |
+
// fire vetoable 'validate' event
|
104 |
+
this.trigger('form-submit-validate', [a, this, options, veto]);
|
105 |
+
if (veto.veto) {
|
106 |
+
log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
|
107 |
+
return this;
|
108 |
+
}
|
109 |
+
|
110 |
+
var q = $.param(a);
|
111 |
+
|
112 |
+
if (options.type.toUpperCase() == 'GET') {
|
113 |
+
options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
|
114 |
+
options.data = null; // data is null for 'get'
|
115 |
+
}
|
116 |
+
else
|
117 |
+
options.data = q; // data is the query string for 'post'
|
118 |
+
|
119 |
+
var $form = this, callbacks = [];
|
120 |
+
if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
|
121 |
+
if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
|
122 |
+
|
123 |
+
// perform a load on the target only if dataType is not provided
|
124 |
+
if (!options.dataType && options.target) {
|
125 |
+
var oldSuccess = options.success || function(){};
|
126 |
+
callbacks.push(function(data) {
|
127 |
+
var fn = options.replaceTarget ? 'replaceWith' : 'html';
|
128 |
+
$(options.target)[fn](data).each(oldSuccess, arguments);
|
129 |
+
});
|
130 |
+
}
|
131 |
+
else if (options.success)
|
132 |
+
callbacks.push(options.success);
|
133 |
+
|
134 |
+
options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
|
135 |
+
for (var i=0, max=callbacks.length; i < max; i++)
|
136 |
+
callbacks[i].apply(options, [data, status, xhr || $form, $form]);
|
137 |
+
};
|
138 |
+
|
139 |
+
// are there files to upload?
|
140 |
+
var files = $('input:file', this).fieldValue();
|
141 |
+
var found = false;
|
142 |
+
for (var j=0; j < files.length; j++)
|
143 |
+
if (files[j])
|
144 |
+
found = true;
|
145 |
+
|
146 |
+
var multipart = false;
|
147 |
+
// var mp = 'multipart/form-data';
|
148 |
+
// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
|
149 |
+
|
150 |
+
// options.iframe allows user to force iframe mode
|
151 |
+
// 06-NOV-09: now defaulting to iframe mode if file input is detected
|
152 |
+
if ((files.length && options.iframe !== false) || options.iframe || found || multipart) {
|
153 |
+
// hack to fix Safari hang (thanks to Tim Molendijk for this)
|
154 |
+
// see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
|
155 |
+
if (options.closeKeepAlive)
|
156 |
+
$.get(options.closeKeepAlive, fileUpload);
|
157 |
+
else
|
158 |
+
fileUpload();
|
159 |
+
}
|
160 |
+
else
|
161 |
+
$.ajax(options);
|
162 |
+
|
163 |
+
// fire 'notify' event
|
164 |
+
this.trigger('form-submit-notify', [this, options]);
|
165 |
+
return this;
|
166 |
+
|
167 |
+
|
168 |
+
// private function for handling file uploads (hat tip to YAHOO!)
|
169 |
+
function fileUpload() {
|
170 |
+
var form = $form[0];
|
171 |
+
|
172 |
+
if ($(':input[name=submit]', form).length) {
|
173 |
+
alert('Error: Form elements must not be named "submit".');
|
174 |
+
return;
|
175 |
+
}
|
176 |
+
|
177 |
+
var opts = $.extend({}, $.ajaxSettings, options);
|
178 |
+
var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
|
179 |
+
|
180 |
+
var id = 'jqFormIO' + (new Date().getTime());
|
181 |
+
var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ opts.iframeSrc +'" onload="(jQuery(this).data(\'form-plugin-onload\'))()" />');
|
182 |
+
var io = $io[0];
|
183 |
+
|
184 |
+
$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
|
185 |
+
|
186 |
+
var xhr = { // mock object
|
187 |
+
aborted: 0,
|
188 |
+
responseText: null,
|
189 |
+
responseXML: null,
|
190 |
+
status: 0,
|
191 |
+
statusText: 'n/a',
|
192 |
+
getAllResponseHeaders: function() {},
|
193 |
+
getResponseHeader: function() {},
|
194 |
+
setRequestHeader: function() {},
|
195 |
+
abort: function() {
|
196 |
+
this.aborted = 1;
|
197 |
+
$io.attr('src', opts.iframeSrc); // abort op in progress
|
198 |
+
}
|
199 |
+
};
|
200 |
+
|
201 |
+
var g = opts.global;
|
202 |
+
// trigger ajax global events so that activity/block indicators work like normal
|
203 |
+
if (g && ! $.active++) $.event.trigger("ajaxStart");
|
204 |
+
if (g) $.event.trigger("ajaxSend", [xhr, opts]);
|
205 |
+
|
206 |
+
if (s.beforeSend && s.beforeSend(xhr, s) === false) {
|
207 |
+
s.global && $.active--;
|
208 |
+
return;
|
209 |
+
}
|
210 |
+
if (xhr.aborted)
|
211 |
+
return;
|
212 |
+
|
213 |
+
var cbInvoked = false;
|
214 |
+
var timedOut = 0;
|
215 |
+
|
216 |
+
// add submitting element to data if we know it
|
217 |
+
var sub = form.clk;
|
218 |
+
if (sub) {
|
219 |
+
var n = sub.name;
|
220 |
+
if (n && !sub.disabled) {
|
221 |
+
opts.extraData = opts.extraData || {};
|
222 |
+
opts.extraData[n] = sub.value;
|
223 |
+
if (sub.type == "image") {
|
224 |
+
opts.extraData[n+'.x'] = form.clk_x;
|
225 |
+
opts.extraData[n+'.y'] = form.clk_y;
|
226 |
+
}
|
227 |
+
}
|
228 |
+
}
|
229 |
+
|
230 |
+
// take a breath so that pending repaints get some cpu time before the upload starts
|
231 |
+
function doSubmit() {
|
232 |
+
// make sure form attrs are set
|
233 |
+
var t = $form.attr('target'), a = $form.attr('action');
|
234 |
+
|
235 |
+
// update form attrs in IE friendly way
|
236 |
+
form.setAttribute('target',id);
|
237 |
+
if (form.getAttribute('method') != 'POST')
|
238 |
+
form.setAttribute('method', 'POST');
|
239 |
+
if (form.getAttribute('action') != opts.url)
|
240 |
+
form.setAttribute('action', opts.url);
|
241 |
+
|
242 |
+
// ie borks in some cases when setting encoding
|
243 |
+
if (! opts.skipEncodingOverride) {
|
244 |
+
$form.attr({
|
245 |
+
encoding: 'multipart/form-data',
|
246 |
+
enctype: 'multipart/form-data'
|
247 |
+
});
|
248 |
+
}
|
249 |
+
|
250 |
+
// support timout
|
251 |
+
if (opts.timeout)
|
252 |
+
setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
|
253 |
+
|
254 |
+
// add "extra" data to form if provided in options
|
255 |
+
var extraInputs = [];
|
256 |
+
try {
|
257 |
+
if (opts.extraData)
|
258 |
+
for (var n in opts.extraData)
|
259 |
+
extraInputs.push(
|
260 |
+
$('<input type="hidden" name="'+n+'" value="'+opts.extraData[n]+'" />')
|
261 |
+
.appendTo(form)[0]);
|
262 |
+
|
263 |
+
// add iframe to doc and submit the form
|
264 |
+
$io.appendTo('body');
|
265 |
+
$io.data('form-plugin-onload', cb);
|
266 |
+
form.submit();
|
267 |
+
}
|
268 |
+
finally {
|
269 |
+
// reset attrs and remove "extra" input elements
|
270 |
+
form.setAttribute('action',a);
|
271 |
+
t ? form.setAttribute('target', t) : $form.removeAttr('target');
|
272 |
+
$(extraInputs).remove();
|
273 |
+
}
|
274 |
+
};
|
275 |
+
|
276 |
+
if (opts.forceSync)
|
277 |
+
doSubmit();
|
278 |
+
else
|
279 |
+
setTimeout(doSubmit, 10); // this lets dom updates render
|
280 |
+
|
281 |
+
var domCheckCount = 100;
|
282 |
+
|
283 |
+
function cb() {
|
284 |
+
if (cbInvoked)
|
285 |
+
return;
|
286 |
+
|
287 |
+
var ok = true;
|
288 |
+
try {
|
289 |
+
if (timedOut) throw 'timeout';
|
290 |
+
// extract the server response from the iframe
|
291 |
+
var data, doc;
|
292 |
+
|
293 |
+
doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
|
294 |
+
|
295 |
+
var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
|
296 |
+
log('isXml='+isXml);
|
297 |
+
if (!isXml && (doc.body == null || doc.body.innerHTML == '')) {
|
298 |
+
if (--domCheckCount) {
|
299 |
+
// in some browsers (Opera) the iframe DOM is not always traversable when
|
300 |
+
// the onload callback fires, so we loop a bit to accommodate
|
301 |
+
log('requeing onLoad callback, DOM not available');
|
302 |
+
setTimeout(cb, 250);
|
303 |
+
return;
|
304 |
+
}
|
305 |
+
log('Could not access iframe DOM after 100 tries.');
|
306 |
+
return;
|
307 |
+
}
|
308 |
+
|
309 |
+
log('response detected');
|
310 |
+
cbInvoked = true;
|
311 |
+
xhr.responseText = doc.body ? doc.body.innerHTML : null;
|
312 |
+
xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
|
313 |
+
xhr.getResponseHeader = function(header){
|
314 |
+
var headers = {'content-type': opts.dataType};
|
315 |
+
return headers[header];
|
316 |
+
};
|
317 |
+
|
318 |
+
if (opts.dataType == 'json' || opts.dataType == 'script') {
|
319 |
+
// see if user embedded response in textarea
|
320 |
+
var ta = doc.getElementsByTagName('textarea')[0];
|
321 |
+
if (ta)
|
322 |
+
xhr.responseText = ta.value;
|
323 |
+
else {
|
324 |
+
// account for browsers injecting pre around json response
|
325 |
+
var pre = doc.getElementsByTagName('pre')[0];
|
326 |
+
if (pre)
|
327 |
+
xhr.responseText = pre.innerHTML;
|
328 |
+
}
|
329 |
+
}
|
330 |
+
else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
|
331 |
+
xhr.responseXML = toXml(xhr.responseText);
|
332 |
+
}
|
333 |
+
data = $.httpData(xhr, opts.dataType);
|
334 |
+
}
|
335 |
+
catch(e){
|
336 |
+
log('error caught:',e);
|
337 |
+
ok = false;
|
338 |
+
xhr.error = e;
|
339 |
+
$.handleError(opts, xhr, 'error', e);
|
340 |
+
}
|
341 |
+
|
342 |
+
// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
|
343 |
+
if (ok) {
|
344 |
+
opts.success(data, 'success');
|
345 |
+
if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
|
346 |
+
}
|
347 |
+
if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
|
348 |
+
if (g && ! --$.active) $.event.trigger("ajaxStop");
|
349 |
+
if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
|
350 |
+
|
351 |
+
// clean up
|
352 |
+
setTimeout(function() {
|
353 |
+
$io.removeData('form-plugin-onload');
|
354 |
+
$io.remove();
|
355 |
+
xhr.responseXML = null;
|
356 |
+
}, 100);
|
357 |
+
};
|
358 |
+
|
359 |
+
function toXml(s, doc) {
|
360 |
+
if (window.ActiveXObject) {
|
361 |
+
doc = new ActiveXObject('Microsoft.XMLDOM');
|
362 |
+
doc.async = 'false';
|
363 |
+
doc.loadXML(s);
|
364 |
+
}
|
365 |
+
else
|
366 |
+
doc = (new DOMParser()).parseFromString(s, 'text/xml');
|
367 |
+
return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
|
368 |
+
};
|
369 |
+
};
|
370 |
+
};
|
371 |
+
|
372 |
+
/**
|
373 |
+
* ajaxForm() provides a mechanism for fully automating form submission.
|
374 |
+
*
|
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 |
+
* is used to submit the form).
|
379 |
+
* 2. This method will include the submit element's name/value data (for the element that was
|
380 |
+
* used to submit the form).
|
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
|
384 |
+
* passes the options argument along after properly binding events for submit elements and
|
385 |
+
* the form itself.
|
386 |
+
*/
|
387 |
+
$.fn.ajaxForm = function(options) {
|
388 |
+
return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
|
389 |
+
e.preventDefault();
|
390 |
+
$(this).ajaxSubmit(options);
|
391 |
+
}).bind('click.form-plugin', function(e) {
|
392 |
+
var target = e.target;
|
393 |
+
var $el = $(target);
|
394 |
+
if (!($el.is(":submit,input:image"))) {
|
395 |
+
// is this a child element of the submit el? (ex: a span within a button)
|
396 |
+
var t = $el.closest(':submit');
|
397 |
+
if (t.length == 0)
|
398 |
+
return;
|
399 |
+
target = t[0];
|
400 |
+
}
|
401 |
+
var form = this;
|
402 |
+
form.clk = target;
|
403 |
+
if (target.type == 'image') {
|
404 |
+
if (e.offsetX != undefined) {
|
405 |
+
form.clk_x = e.offsetX;
|
406 |
+
form.clk_y = e.offsetY;
|
407 |
+
} else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
|
408 |
+
var offset = $el.offset();
|
409 |
+
form.clk_x = e.pageX - offset.left;
|
410 |
+
form.clk_y = e.pageY - offset.top;
|
411 |
+
} else {
|
412 |
+
form.clk_x = e.pageX - target.offsetLeft;
|
413 |
+
form.clk_y = e.pageY - target.offsetTop;
|
414 |
+
}
|
415 |
+
}
|
416 |
+
// clear form vars
|
417 |
+
setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
|
418 |
+
});
|
419 |
+
};
|
420 |
+
|
421 |
+
// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
|
422 |
+
$.fn.ajaxFormUnbind = function() {
|
423 |
+
return this.unbind('submit.form-plugin click.form-plugin');
|
424 |
+
};
|
425 |
+
|
426 |
+
/**
|
427 |
+
* formToArray() gathers form element data into an array of objects that can
|
428 |
+
* be passed to any of the following ajax functions: $.get, $.post, or load.
|
429 |
+
* Each object in the array has both a 'name' and 'value' property. An example of
|
430 |
+
* an array for a simple login form might be:
|
431 |
+
*
|
432 |
+
* [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
|
433 |
+
*
|
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 |
+
var a = [];
|
439 |
+
if (this.length == 0) return a;
|
440 |
+
|
441 |
+
var form = this[0];
|
442 |
+
var els = semantic ? form.getElementsByTagName('*') : form.elements;
|
443 |
+
if (!els) return a;
|
444 |
+
for(var i=0, max=els.length; i < max; i++) {
|
445 |
+
var el = els[i];
|
446 |
+
var n = el.name;
|
447 |
+
if (!n) continue;
|
448 |
+
|
449 |
+
if (semantic && form.clk && el.type == "image") {
|
450 |
+
// handle image inputs on the fly when semantic == true
|
451 |
+
if(!el.disabled && form.clk == el) {
|
452 |
+
a.push({name: n, value: $(el).val()});
|
453 |
+
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
|
454 |
+
}
|
455 |
+
continue;
|
456 |
+
}
|
457 |
+
|
458 |
+
var v = $.fieldValue(el, true);
|
459 |
+
if (v && v.constructor == Array) {
|
460 |
+
for(var j=0, jmax=v.length; j < jmax; j++)
|
461 |
+
a.push({name: n, value: v[j]});
|
462 |
+
}
|
463 |
+
else if (v !== null && typeof v != 'undefined')
|
464 |
+
a.push({name: n, value: v});
|
465 |
+
}
|
466 |
+
|
467 |
+
if (!semantic && form.clk) {
|
468 |
+
// input type=='image' are not found in elements array! handle it here
|
469 |
+
var $input = $(form.clk), input = $input[0], n = input.name;
|
470 |
+
if (n && !input.disabled && input.type == 'image') {
|
471 |
+
a.push({name: n, value: $input.val()});
|
472 |
+
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
|
473 |
+
}
|
474 |
+
}
|
475 |
+
return a;
|
476 |
+
};
|
477 |
+
|
478 |
+
/**
|
479 |
+
* Serializes form data into a 'submittable' string. This method will return a string
|
480 |
+
* in the format: name1=value1&name2=value2
|
481 |
+
*/
|
482 |
+
$.fn.formSerialize = function(semantic) {
|
483 |
+
//hand off to jQuery.param for proper encoding
|
484 |
+
return $.param(this.formToArray(semantic));
|
485 |
+
};
|
486 |
+
|
487 |
+
/**
|
488 |
+
* Serializes all field elements in the jQuery object into a query string.
|
489 |
+
* This method will return a string in the format: name1=value1&name2=value2
|
490 |
+
*/
|
491 |
+
$.fn.fieldSerialize = function(successful) {
|
492 |
+
var a = [];
|
493 |
+
this.each(function() {
|
494 |
+
var n = this.name;
|
495 |
+
if (!n) return;
|
496 |
+
var v = $.fieldValue(this, successful);
|
497 |
+
if (v && v.constructor == Array) {
|
498 |
+
for (var i=0,max=v.length; i < max; i++)
|
499 |
+
a.push({name: n, value: v[i]});
|
500 |
+
}
|
501 |
+
else if (v !== null && typeof v != 'undefined')
|
502 |
+
a.push({name: this.name, value: v});
|
503 |
+
});
|
504 |
+
//hand off to jQuery.param for proper encoding
|
505 |
+
return $.param(a);
|
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 |
+
* <input name="A" type="text" />
|
513 |
+
* <input name="A" type="text" />
|
514 |
+
* <input name="B" type="checkbox" value="B1" />
|
515 |
+
* <input name="B" type="checkbox" value="B2"/>
|
516 |
+
* <input name="C" type="radio" value="C1" />
|
517 |
+
* <input name="C" type="radio" value="C2" />
|
518 |
+
* </fieldset></form>
|
519 |
+
*
|
520 |
+
* var v = $(':text').fieldValue();
|
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 = $(':checkbox').fieldValue();
|
527 |
+
* // if neither checkbox is checked
|
528 |
+
* v === undefined
|
529 |
+
* // if both checkboxes are checked
|
530 |
+
* v == ['B1', 'B2']
|
531 |
+
*
|
532 |
+
* var v = $(':radio').fieldValue();
|
533 |
+
* // if neither radio is checked
|
534 |
+
* v === undefined
|
535 |
+
* // if first radio is checked
|
536 |
+
* v == ['C1']
|
537 |
+
*
|
538 |
+
* The successful argument controls whether or not the field element must be 'successful'
|
539 |
+
* (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
|
540 |
+
* The default value of the successful argument is true. If this value is false the value(s)
|
541 |
+
* for each element is returned.
|
542 |
+
*
|
543 |
+
* Note: This method *always* returns an array. If no valid value can be determined the
|
544 |
+
* array will be empty, otherwise it will contain one or more values.
|
545 |
+
*/
|
546 |
+
$.fn.fieldValue = function(successful) {
|
547 |
+
for (var val=[], i=0, max=this.length; i < max; i++) {
|
548 |
+
var el = this[i];
|
549 |
+
var v = $.fieldValue(el, successful);
|
550 |
+
if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
|
551 |
+
continue;
|
552 |
+
v.constructor == Array ? $.merge(val, v) : val.push(v);
|
553 |
+
}
|
554 |
+
return val;
|
555 |
+
};
|
556 |
+
|
557 |
+
/**
|
558 |
+
* Returns the value of the field element.
|
559 |
+
*/
|
560 |
+
$.fieldValue = function(el, successful) {
|
561 |
+
var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
|
562 |
+
if (typeof successful == 'undefined') successful = true;
|
563 |
+
|
564 |
+
if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
|
565 |
+
(t == 'checkbox' || t == 'radio') && !el.checked ||
|
566 |
+
(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
|
567 |
+
tag == 'select' && el.selectedIndex == -1))
|
568 |
+
return null;
|
569 |
+
|
570 |
+
if (tag == 'select') {
|
571 |
+
var index = el.selectedIndex;
|
572 |
+
if (index < 0) return null;
|
573 |
+
var a = [], ops = el.options;
|
574 |
+
var one = (t == 'select-one');
|
575 |
+
var max = (one ? index+1 : ops.length);
|
576 |
+
for(var i=(one ? index : 0); i < max; i++) {
|
577 |
+
var op = ops[i];
|
578 |
+
if (op.selected) {
|
579 |
+
var v = op.value;
|
580 |
+
if (!v) // extra pain for IE...
|
581 |
+
v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
|
582 |
+
if (one) return v;
|
583 |
+
a.push(v);
|
584 |
+
}
|
585 |
+
}
|
586 |
+
return a;
|
587 |
+
}
|
588 |
+
return el.value;
|
589 |
+
};
|
590 |
+
|
591 |
+
/**
|
592 |
+
* Clears the form data. Takes the following actions on the form's input fields:
|
593 |
+
* - input text fields will have their 'value' property set to the empty string
|
594 |
+
* - select elements will have their 'selectedIndex' property set to -1
|
595 |
+
* - checkbox and radio inputs will have their 'checked' property set to false
|
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 |
+
return this.each(function() {
|
601 |
+
$('input,select,textarea', this).clearFields();
|
602 |
+
});
|
603 |
+
};
|
604 |
+
|
605 |
+
/**
|
606 |
+
* Clears the selected form elements.
|
607 |
+
*/
|
608 |
+
$.fn.clearFields = $.fn.clearInputs = function() {
|
609 |
+
return this.each(function() {
|
610 |
+
var t = this.type, tag = this.tagName.toLowerCase();
|
611 |
+
if (t == 'text' || t == 'password' || tag == 'textarea')
|
612 |
+
this.value = '';
|
613 |
+
else if (t == 'checkbox' || t == 'radio')
|
614 |
+
this.checked = false;
|
615 |
+
else if (tag == 'select')
|
616 |
+
this.selectedIndex = -1;
|
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 |
+
return this.each(function() {
|
625 |
+
// guard against an input with the name of 'reset'
|
626 |
+
// note that IE reports the reset function as an 'object'
|
627 |
+
if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
|
628 |
+
this.reset();
|
629 |
+
});
|
630 |
+
};
|
631 |
+
|
632 |
+
/**
|
633 |
+
* Enables or disables any matching elements.
|
634 |
+
*/
|
635 |
+
$.fn.enable = function(b) {
|
636 |
+
if (b == undefined) b = true;
|
637 |
+
return this.each(function() {
|
638 |
+
this.disabled = !b;
|
639 |
+
});
|
640 |
+
};
|
641 |
+
|
642 |
+
/**
|
643 |
+
* Checks/unchecks any matching checkboxes or radio buttons and
|
644 |
+
* selects/deselects and matching option elements.
|
645 |
+
*/
|
646 |
+
$.fn.selected = function(select) {
|
647 |
+
if (select == undefined) select = true;
|
648 |
+
return this.each(function() {
|
649 |
+
var t = this.type;
|
650 |
+
if (t == 'checkbox' || t == 'radio')
|
651 |
+
this.checked = select;
|
652 |
+
else if (this.tagName.toLowerCase() == 'option') {
|
653 |
+
var $sel = $(this).parent('select');
|
654 |
+
if (select && $sel[0] && $sel[0].type == 'select-one') {
|
655 |
+
// deselect all other options
|
656 |
+
$sel.find('option').selected(false);
|
657 |
+
}
|
658 |
+
this.selected = select;
|
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 |
+
if ($.fn.ajaxSubmit.debug) {
|
667 |
+
var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
|
668 |
+
if (window.console && window.console.log)
|
669 |
+
window.console.log(msg);
|
670 |
+
else if (window.opera && window.opera.postError)
|
671 |
+
window.opera.postError(msg);
|
672 |
+
}
|
673 |
+
};
|
674 |
+
|
675 |
+
})(jQuery);
|
jquery.json-1.3.js → js/jquery.json-1.3.js
RENAMED
File without changes
|
js/jquery.sort.js
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* jQuery.fn.sort
|
3 |
+
* --------------
|
4 |
+
* @param Function comparator:
|
5 |
+
* Exactly the same behaviour as [1,2,3].sort(comparator)
|
6 |
+
*
|
7 |
+
* @param Function getSortable
|
8 |
+
* A function that should return the element that is
|
9 |
+
* to be sorted. The comparator will run on the
|
10 |
+
* current collection, but you may want the actual
|
11 |
+
* resulting sort to occur on a parent or another
|
12 |
+
* associated element.
|
13 |
+
*
|
14 |
+
* E.g. $('td').sort(comparator, function(){
|
15 |
+
* return this.parentNode;
|
16 |
+
* })
|
17 |
+
*
|
18 |
+
* The <td>'s parent (<tr>) will be sorted instead
|
19 |
+
* of the <td> itself.
|
20 |
+
*/
|
21 |
+
jQuery.fn.sort = (function(){
|
22 |
+
|
23 |
+
var sort = [].sort;
|
24 |
+
|
25 |
+
return function(comparator, getSortable) {
|
26 |
+
|
27 |
+
getSortable = getSortable || function(){return this;};
|
28 |
+
|
29 |
+
var placements = this.map(function(){
|
30 |
+
|
31 |
+
var sortElement = getSortable.call(this),
|
32 |
+
parentNode = sortElement.parentNode,
|
33 |
+
|
34 |
+
// Since the element itself will change position, we have
|
35 |
+
// to have some way of storing its original position in
|
36 |
+
// the DOM. The easiest way is to have a 'flag' node:
|
37 |
+
nextSibling = parentNode.insertBefore(
|
38 |
+
document.createTextNode(''),
|
39 |
+
sortElement.nextSibling
|
40 |
+
);
|
41 |
+
|
42 |
+
return function() {
|
43 |
+
|
44 |
+
if (parentNode === this) {
|
45 |
+
throw new Error(
|
46 |
+
"You can't sort elements if any one is a descendant of another."
|
47 |
+
);
|
48 |
+
}
|
49 |
+
|
50 |
+
// Insert before flag:
|
51 |
+
parentNode.insertBefore(this, nextSibling);
|
52 |
+
// Remove flag:
|
53 |
+
parentNode.removeChild(nextSibling);
|
54 |
+
|
55 |
+
};
|
56 |
+
|
57 |
+
});
|
58 |
+
|
59 |
+
return sort.call(this, comparator).each(function(i){
|
60 |
+
placements[i].call(getSortable.call(this));
|
61 |
+
});
|
62 |
+
|
63 |
+
};
|
64 |
+
|
65 |
+
})();
|
js/menu-editor.js
ADDED
@@ -0,0 +1,1395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
//(c) W-Shadow
|
2 |
+
|
3 |
+
var wsIdCounter = 0;
|
4 |
+
|
5 |
+
(function ($){
|
6 |
+
|
7 |
+
/*
|
8 |
+
* Utility function for generating pseudo-random alphanumeric menu IDs.
|
9 |
+
* Rationale: Simpler than atomically auto-incrementing or globally unique IDs.
|
10 |
+
*/
|
11 |
+
function randomMenuId(size){
|
12 |
+
if ( typeof size == 'undefined' ){
|
13 |
+
size = 5;
|
14 |
+
}
|
15 |
+
|
16 |
+
var text = "";
|
17 |
+
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
18 |
+
|
19 |
+
for( var i=0; i < size; i++ )
|
20 |
+
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
21 |
+
|
22 |
+
return text;
|
23 |
+
}
|
24 |
+
|
25 |
+
function outputWpMenu(menu){
|
26 |
+
//Remove the current menu data
|
27 |
+
$('#ws_menu_box').empty();
|
28 |
+
$('#ws_submenu_box').empty();
|
29 |
+
//Kill autocomplete boxes
|
30 |
+
$('.ac_results').remove();
|
31 |
+
|
32 |
+
//Display the new menu
|
33 |
+
var i = 0;
|
34 |
+
for (var filename in menu){
|
35 |
+
outputTopMenu(menu[filename]);
|
36 |
+
i++;
|
37 |
+
}
|
38 |
+
}
|
39 |
+
|
40 |
+
/*
|
41 |
+
* Create edit widgets for a top-level menu and its submenus and append them all to the DOM.
|
42 |
+
*
|
43 |
+
* Inputs :
|
44 |
+
* menu - an object containing menu data
|
45 |
+
* afterNode - if specified, the new menu widget will be inserted after this node. Otherwise,
|
46 |
+
* it will be added to the end of the list.
|
47 |
+
* Outputs :
|
48 |
+
* Object with two fields - 'menu' and 'submenu' - containing the DOM nodes of the created widgets.
|
49 |
+
*/
|
50 |
+
function outputTopMenu(menu, afterNode){
|
51 |
+
//Create a container for menu items, even if there are none
|
52 |
+
var submenu = buildSubmenu(menu.items);
|
53 |
+
|
54 |
+
//Create the menu widget
|
55 |
+
var menu_obj = buildTopMenu(menu);
|
56 |
+
menu_obj.data('submenu_id', submenu.attr('id'));
|
57 |
+
|
58 |
+
//Display
|
59 |
+
submenu.appendTo('#ws_submenu_box');
|
60 |
+
if ( typeof afterNode != 'undefined' ){
|
61 |
+
$(afterNode).after(menu_obj);
|
62 |
+
} else {
|
63 |
+
menu_obj.appendTo('#ws_menu_box');
|
64 |
+
}
|
65 |
+
|
66 |
+
return {
|
67 |
+
'menu' : menu_obj,
|
68 |
+
'submenu' : submenu
|
69 |
+
};
|
70 |
+
}
|
71 |
+
|
72 |
+
/*
|
73 |
+
* Create an edit widget for a top-level menu.
|
74 |
+
*/
|
75 |
+
function buildTopMenu(menu){
|
76 |
+
var subclass = '';
|
77 |
+
if ( menu.separator ) {
|
78 |
+
subclass = subclass + ' ws_menu_separator';
|
79 |
+
}
|
80 |
+
|
81 |
+
//Create the menu HTML
|
82 |
+
var menu_obj = $('<div></div>')
|
83 |
+
.attr('class', "ws_container ws_menu "+subclass)
|
84 |
+
.attr('id', 'ws-topmenu-'+(wsIdCounter++))
|
85 |
+
.data('defaults', menu.defaults)
|
86 |
+
.data('initial_values', getCurrentFieldValues(menu))
|
87 |
+
.data('field_editors_created', false);
|
88 |
+
|
89 |
+
//Add a header and a container for property editors (to improve performance
|
90 |
+
//the editors themselves are created later, when the user tries to access them
|
91 |
+
//for the first time).
|
92 |
+
var contents = [];
|
93 |
+
contents.push(
|
94 |
+
'<div class="ws_item_head">',
|
95 |
+
'<a class="ws_edit_link"> </a><div class="ws_flag_container"> </div>',
|
96 |
+
'<span class="ws_item_title">',
|
97 |
+
((menu.menu_title!=null)?menu.menu_title:menu.defaults.menu_title),
|
98 |
+
' </span>',
|
99 |
+
'</div>',
|
100 |
+
'<div class="ws_editbox" style="display: none;"></div>'
|
101 |
+
);
|
102 |
+
menu_obj.append(contents.join(''));
|
103 |
+
|
104 |
+
//Apply flags based on the item's state
|
105 |
+
if (menu.missing && !getFieldValue(menu, 'custom', false)) {
|
106 |
+
addMenuFlag(menu_obj, 'missing');
|
107 |
+
}
|
108 |
+
if (menu.hidden) {
|
109 |
+
addMenuFlag(menu_obj, 'hidden');
|
110 |
+
}
|
111 |
+
if (menu.unused) {
|
112 |
+
addMenuFlag(menu_obj, 'unused');
|
113 |
+
}
|
114 |
+
if (getFieldValue(menu, 'custom', false)) {
|
115 |
+
addMenuFlag(menu_obj, 'custom_item');
|
116 |
+
}
|
117 |
+
|
118 |
+
return menu_obj;
|
119 |
+
}
|
120 |
+
|
121 |
+
/*
|
122 |
+
* Create and populate a submenu container.
|
123 |
+
*/
|
124 |
+
function buildSubmenu(items){
|
125 |
+
//Create a container for menu items, even if there are none
|
126 |
+
var submenu = $('<div class="ws_submenu"style="display:none;"></div>');
|
127 |
+
submenu.attr('id', 'ws-submenu-'+(wsIdCounter++));
|
128 |
+
|
129 |
+
//Only show menus that have items.
|
130 |
+
//Skip arrays (with a length) because filled menus are encoded as custom objects.
|
131 |
+
var entry = null
|
132 |
+
if (items && (typeof items != 'Array')){
|
133 |
+
for (var item_file in items){
|
134 |
+
entry = buildMenuItem(items[item_file]);
|
135 |
+
if ( entry ){
|
136 |
+
submenu.append(entry);
|
137 |
+
}
|
138 |
+
}
|
139 |
+
}
|
140 |
+
|
141 |
+
//Make the submenu sortable
|
142 |
+
makeBoxSortable(submenu);
|
143 |
+
|
144 |
+
return submenu;
|
145 |
+
}
|
146 |
+
|
147 |
+
/*
|
148 |
+
* Create an edit widget for a menu entry and return it.
|
149 |
+
*/
|
150 |
+
function buildMenuItem(entry){
|
151 |
+
if (!entry.defaults) {
|
152 |
+
return null
|
153 |
+
};
|
154 |
+
|
155 |
+
var item = $('<div class="ws_container ws_item">')
|
156 |
+
.data('defaults', entry.defaults)
|
157 |
+
.data('initial_values', getCurrentFieldValues(entry))
|
158 |
+
.data('field_editors_created', false);
|
159 |
+
|
160 |
+
//Add a header and a container for property editors (to improve performance
|
161 |
+
//the editors themselves are created later, when the user tries to access them
|
162 |
+
//for the first time).
|
163 |
+
var contents = [];
|
164 |
+
contents.push(
|
165 |
+
'<div class="ws_item_head">',
|
166 |
+
'<a class="ws_edit_link"> </a><div class="ws_flag_container"> </div>',
|
167 |
+
'<span class="ws_item_title">',
|
168 |
+
((entry.menu_title!=null)?entry.menu_title:entry.defaults.menu_title),
|
169 |
+
' </span>',
|
170 |
+
'</div>',
|
171 |
+
'<div class="ws_editbox" style="display: none;"></div>'
|
172 |
+
);
|
173 |
+
item.append(contents.join(''));
|
174 |
+
|
175 |
+
//Apply flags based on the item's state
|
176 |
+
if (entry.missing && !getFieldValue(entry, 'custom', false)) {
|
177 |
+
addMenuFlag(item, 'missing');
|
178 |
+
}
|
179 |
+
if (entry.hidden) {
|
180 |
+
addMenuFlag(item, 'hidden');
|
181 |
+
}
|
182 |
+
if (entry.unused) {
|
183 |
+
addMenuFlag(item, 'unused');
|
184 |
+
}
|
185 |
+
if (getFieldValue(entry, 'custom', false)) {
|
186 |
+
addMenuFlag(item, 'custom_item');
|
187 |
+
}
|
188 |
+
|
189 |
+
return item;
|
190 |
+
}
|
191 |
+
|
192 |
+
/*
|
193 |
+
* List of all menu fields that have an associated editor
|
194 |
+
*/
|
195 |
+
var knownMenuFields = {
|
196 |
+
'menu_title' : {
|
197 |
+
caption : 'Menu title',
|
198 |
+
standardCaption : true,
|
199 |
+
advanced : false,
|
200 |
+
type : 'text',
|
201 |
+
defaultValue: '',
|
202 |
+
visible: true
|
203 |
+
},
|
204 |
+
'access_level' : {
|
205 |
+
caption: 'Required capability',
|
206 |
+
standardCaption : true,
|
207 |
+
advanced : false,
|
208 |
+
type : 'text',
|
209 |
+
defaultValue: 'read',
|
210 |
+
addDropdown : true,
|
211 |
+
visible: true
|
212 |
+
},
|
213 |
+
'file' : {
|
214 |
+
caption: 'URL',
|
215 |
+
advanced : false,
|
216 |
+
standardCaption : true,
|
217 |
+
type : 'text',
|
218 |
+
defaultValue: '',
|
219 |
+
visible: true
|
220 |
+
},
|
221 |
+
'page_title' : {
|
222 |
+
caption: "Window title",
|
223 |
+
standardCaption : true,
|
224 |
+
advanced : true,
|
225 |
+
type : 'text',
|
226 |
+
defaultValue: '',
|
227 |
+
visible: true
|
228 |
+
},
|
229 |
+
'open_in' : {
|
230 |
+
caption: 'Open in',
|
231 |
+
standardCaption : true,
|
232 |
+
advanced : true,
|
233 |
+
type : 'select',
|
234 |
+
options : {
|
235 |
+
'Same window or tab' : 'same_window',
|
236 |
+
'New window' : 'new_window',
|
237 |
+
'Frame' : 'iframe'
|
238 |
+
},
|
239 |
+
defaultValue: 'same_window',
|
240 |
+
visible: false
|
241 |
+
},
|
242 |
+
'css_class' : {
|
243 |
+
caption: 'CSS classes',
|
244 |
+
standardCaption : true,
|
245 |
+
advanced : true,
|
246 |
+
type : 'text',
|
247 |
+
defaultValue: '',
|
248 |
+
visible: true
|
249 |
+
},
|
250 |
+
'hookname' : {
|
251 |
+
caption: 'Hook name',
|
252 |
+
standardCaption : true,
|
253 |
+
advanced : true,
|
254 |
+
type : 'text',
|
255 |
+
defaultValue: '',
|
256 |
+
visible: true
|
257 |
+
},
|
258 |
+
'icon_url' : {
|
259 |
+
caption: 'Icon URL',
|
260 |
+
standardCaption : true,
|
261 |
+
advanced : true,
|
262 |
+
type : 'text',
|
263 |
+
defaultValue: 'div',
|
264 |
+
visible: true
|
265 |
+
},
|
266 |
+
'custom' : {
|
267 |
+
caption : 'Custom',
|
268 |
+
standardCaption : false,
|
269 |
+
advanced: true,
|
270 |
+
type: 'checkbox',
|
271 |
+
defaultValue: false,
|
272 |
+
visible: true
|
273 |
+
}
|
274 |
+
};
|
275 |
+
|
276 |
+
/*
|
277 |
+
* Create editors for the visible fields of a menu entry and append them to the specified node.
|
278 |
+
*/
|
279 |
+
function buildEditboxFields(containerNode, entry){
|
280 |
+
var fields = knownMenuFields;
|
281 |
+
|
282 |
+
var basicFields = $('<div class="ws_edit_panel ws_basic"></div>').appendTo(containerNode);
|
283 |
+
var advancedFields = $('<div class="ws_edit_panel ws_advanced"></div>').appendTo(containerNode);
|
284 |
+
|
285 |
+
if ( hideAdvancedSettings ){
|
286 |
+
advancedFields.css('display', 'none');
|
287 |
+
}
|
288 |
+
|
289 |
+
for (var field_name in fields){
|
290 |
+
var field = buildEditboxField(entry, field_name, fields[field_name]);
|
291 |
+
if (field){
|
292 |
+
if (fields[field_name].advanced){
|
293 |
+
advancedFields.append(field);
|
294 |
+
} else {
|
295 |
+
basicFields.append(field);
|
296 |
+
}
|
297 |
+
}
|
298 |
+
}
|
299 |
+
|
300 |
+
//Add a link that shows/hides advanced fields
|
301 |
+
containerNode.append(
|
302 |
+
'<div class="ws_toggle_container"><a href="#" class="ws_toggle_advanced_fields"'+
|
303 |
+
(hideAdvancedSettings?'':' style="display:none;"')+'>'+
|
304 |
+
(hideAdvancedSettings?captionShowAdvanced:captionHideAdvanced)
|
305 |
+
+'</a></div>'
|
306 |
+
);
|
307 |
+
}
|
308 |
+
|
309 |
+
/*
|
310 |
+
* Create an editor for a specified field.
|
311 |
+
*/
|
312 |
+
function buildEditboxField(entry, field_name, field_settings){
|
313 |
+
if (typeof entry[field_name] === 'undefined') {
|
314 |
+
return null; //skip fields this entry doesn't have
|
315 |
+
}
|
316 |
+
|
317 |
+
var default_value = (typeof entry.defaults[field_name] != 'undefined')?entry.defaults[field_name]:field_settings.defaultValue;
|
318 |
+
var value = (entry[field_name]!=null)?entry[field_name]:default_value;
|
319 |
+
|
320 |
+
//Build a form field of the appropriate type
|
321 |
+
var inputBox = null;
|
322 |
+
switch(field_settings.type){
|
323 |
+
case 'select':
|
324 |
+
inputBox = $('<select class="ws_field_value">');
|
325 |
+
var option = null;
|
326 |
+
for( var optionTitle in field_settings.options ){
|
327 |
+
option = $('<option>')
|
328 |
+
.val(field_settings.options[optionTitle])
|
329 |
+
.text(optionTitle);
|
330 |
+
if ( field_settings.options[optionTitle] == value ){
|
331 |
+
option.attr('selected', 'selected');
|
332 |
+
}
|
333 |
+
option.appendTo(inputBox);
|
334 |
+
}
|
335 |
+
break;
|
336 |
+
|
337 |
+
case 'checkbox':
|
338 |
+
inputBox = $('<label><input type="checkbox"'+(value?' checked="checked"':'')+ ' class="ws_field_value"> '+
|
339 |
+
field_settings.caption+'</label>'
|
340 |
+
);
|
341 |
+
break;
|
342 |
+
|
343 |
+
case 'text':
|
344 |
+
default:
|
345 |
+
inputBox = $('<input type="text" class="ws_field_value">').val(value);
|
346 |
+
}
|
347 |
+
|
348 |
+
|
349 |
+
var className = "ws_edit_field ws_edit_field-"+field_name;
|
350 |
+
if(entry[field_name]==null){
|
351 |
+
className += ' ws_input_default';
|
352 |
+
}
|
353 |
+
|
354 |
+
var editField = $('<div>' + (field_settings.standardCaption?(field_settings.caption+'<br>'):'') + '</div>')
|
355 |
+
.attr('class', className)
|
356 |
+
.append(inputBox);
|
357 |
+
|
358 |
+
if ( (typeof(field_settings['addDropdown']) != 'undefined') && field_settings.addDropdown ){
|
359 |
+
//Add a dropdown button
|
360 |
+
editField.append('<input type="button" class="button ws_dropdown_button" value="▼" tabindex="-1">');
|
361 |
+
}
|
362 |
+
|
363 |
+
editField
|
364 |
+
.append('<img src="'+imagesUrl+'/transparent16.png" class="ws_reset_button" title="Reset to default value"> </img>')
|
365 |
+
.data('field_name', field_name)
|
366 |
+
.data('default_value', default_value);
|
367 |
+
|
368 |
+
if ( !field_settings.visible ){
|
369 |
+
editField.css('display', 'none');
|
370 |
+
}
|
371 |
+
|
372 |
+
return editField;
|
373 |
+
}
|
374 |
+
|
375 |
+
/*
|
376 |
+
* Get the current values of all menu fields (ignores defaults).
|
377 |
+
* Returns an object containing each field as a separate property.
|
378 |
+
*/
|
379 |
+
function getCurrentFieldValues(entry){
|
380 |
+
var values = {};
|
381 |
+
|
382 |
+
for (var field_name in knownMenuFields){
|
383 |
+
if (typeof entry[field_name] === 'undefined') {
|
384 |
+
continue; //skip fields this entry doesn't have
|
385 |
+
}
|
386 |
+
values[field_name] = entry[field_name];
|
387 |
+
}
|
388 |
+
|
389 |
+
values.defaults = entry.defaults;
|
390 |
+
|
391 |
+
return values;
|
392 |
+
}
|
393 |
+
|
394 |
+
/*
|
395 |
+
* Get the current value of a single menu field.
|
396 |
+
*
|
397 |
+
* If the specified field is not set, this function will attempt to retrieve it
|
398 |
+
* from the "defaults" property of the menu object. If *that* fails, it will return
|
399 |
+
* the value of the optional third argument defaultValue.
|
400 |
+
*/
|
401 |
+
function getFieldValue(entry, fieldName, defaultValue){
|
402 |
+
if ( (typeof entry[fieldName] === 'undefined') || (entry[fieldName] === null) ) {
|
403 |
+
if ( (typeof entry['defaults'] === 'undefined') || (typeof entry['defaults'][fieldName] === 'undefined') ){
|
404 |
+
return defaultValue;
|
405 |
+
} else {
|
406 |
+
return entry.defaults[fieldName];
|
407 |
+
}
|
408 |
+
} else {
|
409 |
+
return entry[fieldName];
|
410 |
+
}
|
411 |
+
}
|
412 |
+
|
413 |
+
/*
|
414 |
+
* Make a menu container sortable
|
415 |
+
*/
|
416 |
+
function makeBoxSortable(menuBox){
|
417 |
+
//Make the submenu sortable
|
418 |
+
menuBox.sortable({
|
419 |
+
items: '> .ws_container',
|
420 |
+
cursor: 'move',
|
421 |
+
dropOnEmpty: true,
|
422 |
+
cancel : '.ws_editbox, .ws_edit_link'
|
423 |
+
});
|
424 |
+
}
|
425 |
+
|
426 |
+
/***************************************************************************
|
427 |
+
Parsing & encoding menu inputs
|
428 |
+
***************************************************************************/
|
429 |
+
|
430 |
+
/*
|
431 |
+
* Encode the current menu structure as JSON
|
432 |
+
*
|
433 |
+
* Returns :
|
434 |
+
* A JSON-encoded string representing the current menu tree loaded in the editor.
|
435 |
+
*/
|
436 |
+
function encodeMenuAsJSON(){
|
437 |
+
var tree = readMenuTreeState();
|
438 |
+
return $.toJSON(tree);
|
439 |
+
}
|
440 |
+
|
441 |
+
function readMenuTreeState(){
|
442 |
+
var tree = {};
|
443 |
+
var menu_position = 0;
|
444 |
+
|
445 |
+
//Gather all menus and their items
|
446 |
+
$('#ws_menu_box .ws_menu').each(function(i) {
|
447 |
+
var menu = readMenuState(this, menu_position++);
|
448 |
+
|
449 |
+
//Attach the current menu to the main struct
|
450 |
+
var filename = (menu.file !== null)?menu.file:menu.defaults.file;
|
451 |
+
tree[filename] = menu;
|
452 |
+
});
|
453 |
+
|
454 |
+
return tree;
|
455 |
+
}
|
456 |
+
|
457 |
+
/*
|
458 |
+
* Extract the settings of a top-level menu from its editor widget(s).
|
459 |
+
*
|
460 |
+
* Inputs :
|
461 |
+
* menu_div - DOM node, typically one with the .ws_menu class.
|
462 |
+
* position - The current menu position (int).
|
463 |
+
*
|
464 |
+
* Output :
|
465 |
+
* A menu object in the tree format.
|
466 |
+
*
|
467 |
+
*/
|
468 |
+
function readMenuState(menu_div, position){
|
469 |
+
menu_div = $(menu_div);
|
470 |
+
var menu = readAllFields(menu_div);
|
471 |
+
|
472 |
+
menu.defaults = menu_div.data('defaults');
|
473 |
+
|
474 |
+
menu.position = position;
|
475 |
+
menu.defaults.position = position; //the real default value will later overwrite this
|
476 |
+
|
477 |
+
menu.separator = menu_div.hasClass('ws_menu_separator');
|
478 |
+
menu.hidden = menu_div.hasClass('ws_hidden');
|
479 |
+
|
480 |
+
//Gather the menu's items, if any
|
481 |
+
menu.items = {};
|
482 |
+
var item_position = 0;
|
483 |
+
$('#'+menu_div.data('submenu_id')).find('.ws_item').each(function (i) {
|
484 |
+
var item = readItemState(this, item_position++);
|
485 |
+
menu.items[ (item.file?item.file:item.defaults.file) ] = item;
|
486 |
+
});
|
487 |
+
|
488 |
+
return menu;
|
489 |
+
}
|
490 |
+
|
491 |
+
/*
|
492 |
+
* Extract the current menu item settings from its editor widget.
|
493 |
+
*
|
494 |
+
* Inputs :
|
495 |
+
* item_div - DOM node containing the editor widget, usually with the .ws_item class.
|
496 |
+
* position - Menu item position among its sibling menu items.
|
497 |
+
*
|
498 |
+
*/
|
499 |
+
function readItemState(item_div, position){
|
500 |
+
var item_div = $(item_div);
|
501 |
+
var item = readAllFields(item_div);
|
502 |
+
|
503 |
+
item.defaults = item_div.data('defaults');
|
504 |
+
|
505 |
+
//Save the position data
|
506 |
+
if ( typeof position == 'undefined' ){
|
507 |
+
position = 0;
|
508 |
+
}
|
509 |
+
item.position = position;
|
510 |
+
item.defaults.position = position;
|
511 |
+
|
512 |
+
//Check if the item is hidden
|
513 |
+
item.hidden = item_div.hasClass('ws_hidden');
|
514 |
+
|
515 |
+
return item;
|
516 |
+
}
|
517 |
+
|
518 |
+
/*
|
519 |
+
* Extract the values of all menu/item fields present in a container node
|
520 |
+
*
|
521 |
+
* Inputs:
|
522 |
+
* container - a jQuery collection representing the node to read.
|
523 |
+
*/
|
524 |
+
function readAllFields(container){
|
525 |
+
if ( !container.hasClass('ws_container') ){
|
526 |
+
container = container.parents('ws_container').first();
|
527 |
+
}
|
528 |
+
|
529 |
+
if ( !container.data('field_editors_created') ){
|
530 |
+
return container.data('initial_values');
|
531 |
+
}
|
532 |
+
|
533 |
+
var state = {};
|
534 |
+
|
535 |
+
//Iterate over all fields of the item
|
536 |
+
container.find('.ws_edit_field').each(function() {
|
537 |
+
var field = $(this);
|
538 |
+
|
539 |
+
//Get the name of this field
|
540 |
+
field_name = field.data('field_name');
|
541 |
+
//Skip if unnamed
|
542 |
+
if (!field_name) return true;
|
543 |
+
|
544 |
+
//Find the field (usually an input or select element).
|
545 |
+
input_box = field.find('.ws_field_value');
|
546 |
+
|
547 |
+
//Save null if default used, custom value otherwise
|
548 |
+
if (field.hasClass('ws_input_default')){
|
549 |
+
state[field_name] = null;
|
550 |
+
} else {
|
551 |
+
if ( input_box.attr('type') == 'checkbox' ){
|
552 |
+
state[field_name] = input_box.is(':checked');
|
553 |
+
} else {
|
554 |
+
state[field_name] = input_box.val();
|
555 |
+
}
|
556 |
+
}
|
557 |
+
});
|
558 |
+
|
559 |
+
return state;
|
560 |
+
}
|
561 |
+
|
562 |
+
|
563 |
+
/***************************************************************************
|
564 |
+
Flag manipulation
|
565 |
+
***************************************************************************/
|
566 |
+
|
567 |
+
var item_flags = {
|
568 |
+
'custom_item' : 'This is a custom menu item',
|
569 |
+
'unused' : 'This item was automatically (re)inserted into your custom menu because it is present in the default WordPress menu',
|
570 |
+
'missing' : 'This item is not present in the default WordPress menu. Tick the "Custom" checkbox if you want it to be visible anyway.',
|
571 |
+
'hidden' : 'This item is hidden'
|
572 |
+
}
|
573 |
+
|
574 |
+
function addMenuFlag(item, flag){
|
575 |
+
item = $(item);
|
576 |
+
|
577 |
+
var item_class = 'ws_' + flag;
|
578 |
+
var img_class = 'ws_' + flag + '_flag';
|
579 |
+
|
580 |
+
item.addClass(item_class);
|
581 |
+
//Add the flag image
|
582 |
+
var flag_container = item.find('.ws_flag_container');
|
583 |
+
if ( flag_container.find('.' + img_class).length == 0 ){
|
584 |
+
flag_container.append('<div class="ws_flag '+img_class+'" title="'+item_flags[flag]+'"></div>');
|
585 |
+
}
|
586 |
+
}
|
587 |
+
|
588 |
+
function removeMenuFlag(item, flag){
|
589 |
+
item = $(item);
|
590 |
+
var item_class = 'ws_' + flag;
|
591 |
+
var img_class = 'ws_' + flag + '_flag';
|
592 |
+
|
593 |
+
item.removeClass('ws_' + flag);
|
594 |
+
item.find('.' + img_class).remove();
|
595 |
+
}
|
596 |
+
|
597 |
+
function toggleMenuFlag(item, flag){
|
598 |
+
if (menuHasFlag(item, flag)){
|
599 |
+
removeMenuFlag(item, flag);
|
600 |
+
} else {
|
601 |
+
addMenuFlag(item, flag);
|
602 |
+
}
|
603 |
+
}
|
604 |
+
|
605 |
+
function menuHasFlag(item, flag){
|
606 |
+
return $(item).hasClass('ws_'+flag);
|
607 |
+
}
|
608 |
+
|
609 |
+
function clearMenuFlags(item){
|
610 |
+
item = $(item);
|
611 |
+
item.find('.ws_flag').remove();
|
612 |
+
for(var flag in item_flags){
|
613 |
+
item.removeClass('ws_'+flag);
|
614 |
+
}
|
615 |
+
}
|
616 |
+
|
617 |
+
//Cut & paste stuff
|
618 |
+
var menu_in_clipboard = null;
|
619 |
+
var submenu_in_clipboard = null;
|
620 |
+
var item_in_clipboard = null;
|
621 |
+
var ws_paste_count = 0;
|
622 |
+
|
623 |
+
$(document).ready(function(){
|
624 |
+
if (window.wsMenuEditorPro) {
|
625 |
+
knownMenuFields['open_in'].visible = true;
|
626 |
+
};
|
627 |
+
|
628 |
+
//Show the menu
|
629 |
+
outputWpMenu(customMenu);
|
630 |
+
|
631 |
+
//Make the top menu box sortable (we only need to do this once)
|
632 |
+
var mainMenuBox = $('#ws_menu_box');
|
633 |
+
makeBoxSortable(mainMenuBox);
|
634 |
+
|
635 |
+
/***************************************************************************
|
636 |
+
Event handlers for editor widgets
|
637 |
+
***************************************************************************/
|
638 |
+
|
639 |
+
//Highlight the clicked menu item and show it's submenu
|
640 |
+
var currentVisibleSubmenu = null;
|
641 |
+
$('#ws_menu_editor .ws_container').live('click', (function () {
|
642 |
+
var container = $(this);
|
643 |
+
if ( container.hasClass('ws_active') ){
|
644 |
+
return;
|
645 |
+
}
|
646 |
+
|
647 |
+
//Highlight the active item and un-highlight the previous one
|
648 |
+
container.addClass('ws_active')
|
649 |
+
container.siblings('.ws_active').removeClass('ws_active');
|
650 |
+
if ( container.hasClass('ws_menu') ){
|
651 |
+
//Show/hide the appropriate submenu
|
652 |
+
if ( currentVisibleSubmenu ){
|
653 |
+
currentVisibleSubmenu.hide();
|
654 |
+
}
|
655 |
+
currentVisibleSubmenu = $('#'+container.data('submenu_id')).show();
|
656 |
+
}
|
657 |
+
}));
|
658 |
+
|
659 |
+
//Show/hide a menu's properties
|
660 |
+
$('#ws_menu_editor .ws_edit_link').live('click', (function () {
|
661 |
+
var container = $(this).parents('.ws_container').first();
|
662 |
+
var box = container.find('.ws_editbox');
|
663 |
+
|
664 |
+
//For performance, the property editors for each menu are only created
|
665 |
+
//when the user tries to access access them for the first time.
|
666 |
+
if ( !container.data('field_editors_created') ){
|
667 |
+
buildEditboxFields(box, container.data('initial_values'));
|
668 |
+
container.data('field_editors_created', true);
|
669 |
+
}
|
670 |
+
|
671 |
+
$(this).toggleClass('ws_edit_link_expanded');
|
672 |
+
//show/hide the editbox
|
673 |
+
if ($(this).hasClass('ws_edit_link_expanded')){
|
674 |
+
box.show();
|
675 |
+
} else {
|
676 |
+
//Make sure changes are applied before the menu is collapsed
|
677 |
+
box.find('input').change();
|
678 |
+
box.hide();
|
679 |
+
}
|
680 |
+
}));
|
681 |
+
|
682 |
+
//The "Default" button : Reset to default value when clicked
|
683 |
+
$('#ws_menu_editor .ws_reset_button').live('click', (function () {
|
684 |
+
//Find the field div (it holds the default value)
|
685 |
+
var field = $(this).parent();
|
686 |
+
//Find the related input field
|
687 |
+
var input = field.find('.ws_field_value');
|
688 |
+
if ( (input.length > 0) && (field.length > 0) ) {
|
689 |
+
//Set the value to the default
|
690 |
+
if (input.attr('type') == 'checkbox'){
|
691 |
+
if ( field.data('default_value') ){
|
692 |
+
input.attr('checked', 'checked');
|
693 |
+
} else {
|
694 |
+
input.removeAttr('checked');
|
695 |
+
}
|
696 |
+
} else {
|
697 |
+
input.val(field.data('default_value'));
|
698 |
+
}
|
699 |
+
field.addClass('ws_input_default');
|
700 |
+
//Trigger the change event to ensure consistency
|
701 |
+
input.change();
|
702 |
+
}
|
703 |
+
}));
|
704 |
+
|
705 |
+
//When a field is edited, change it's appearance if it's contents don't match the default value.
|
706 |
+
function fieldValueChange(){
|
707 |
+
var input = $(this);
|
708 |
+
var field = input.parents('.ws_edit_field').first();
|
709 |
+
|
710 |
+
if ( input.attr('type') == 'checkbox' ){
|
711 |
+
var value = input.is(':checked');
|
712 |
+
} else {
|
713 |
+
var value = input.val();
|
714 |
+
}
|
715 |
+
|
716 |
+
if ( field.data('default_value') != value ) {
|
717 |
+
field.removeClass('ws_input_default');
|
718 |
+
}
|
719 |
+
|
720 |
+
var fieldName = field.data('field_name');
|
721 |
+
if ( fieldName == 'menu_title' ){
|
722 |
+
//If the changed field is the menu title, update the header
|
723 |
+
field.parents('.ws_container').first().find('.ws_item_title').html(input.val()+' ');
|
724 |
+
} else if ( fieldName == 'custom' ){
|
725 |
+
//Show/hide the custom flag
|
726 |
+
var myContainer = field.parents('.ws_container').first();
|
727 |
+
if ( value ){
|
728 |
+
addMenuFlag(myContainer, 'custom_item');
|
729 |
+
} else {
|
730 |
+
removeMenuFlag(myContainer, 'custom_item');
|
731 |
+
}
|
732 |
+
} else if (fieldName == 'file' ){
|
733 |
+
//A menu must always have a non-empty URL. If the user deletes the current value,
|
734 |
+
//reset back to the default.
|
735 |
+
if ( value == '' ){
|
736 |
+
field.find('.ws_reset_button').click();
|
737 |
+
}
|
738 |
+
}
|
739 |
+
}
|
740 |
+
$('#ws_menu_editor .ws_field_value').live('click', fieldValueChange);
|
741 |
+
//jQuery 1.3.x can't catch 'change' events with live(),
|
742 |
+
//so we handle that by using event delegation instead.
|
743 |
+
$('#ws_menu_editor').change(function(event){
|
744 |
+
var target = $(event.target);
|
745 |
+
if ( target.is('.ws_field_value') ){
|
746 |
+
fieldValueChange.call(target, event);
|
747 |
+
}
|
748 |
+
});
|
749 |
+
|
750 |
+
//Show/hide advanced fields
|
751 |
+
$('#ws_menu_editor .ws_toggle_advanced_fields').live('click', function(){
|
752 |
+
var self = $(this);
|
753 |
+
var advancedFields = self.parents('.ws_container').first().find('.ws_advanced');
|
754 |
+
|
755 |
+
if ( advancedFields.is(':visible') ){
|
756 |
+
advancedFields.hide();
|
757 |
+
self.text(captionShowAdvanced);
|
758 |
+
} else {
|
759 |
+
advancedFields.show();
|
760 |
+
self.text(captionHideAdvanced);
|
761 |
+
}
|
762 |
+
|
763 |
+
return false;
|
764 |
+
});
|
765 |
+
|
766 |
+
|
767 |
+
//Event handlers for the capability dropdown
|
768 |
+
var capList = $('#ws_cap_selector');
|
769 |
+
var currentCapListOwner = null;
|
770 |
+
var timeoutForgetOwner = 0;
|
771 |
+
|
772 |
+
//Show/hide the capability dropdown list when the button is clicked
|
773 |
+
$('#ws_menu_editor input.ws_dropdown_button').live('click',function(event){
|
774 |
+
var list = capList;
|
775 |
+
|
776 |
+
var button = $(this);
|
777 |
+
var inputBox = button.parent().find('input.ws_field_value');
|
778 |
+
|
779 |
+
clearTimeout(timeoutForgetOwner);
|
780 |
+
timeoutForgetOwner = 0;
|
781 |
+
|
782 |
+
//If we already own the list, hide it and rescind ownership.
|
783 |
+
if ( currentCapListOwner == this ){
|
784 |
+
list.hide();
|
785 |
+
|
786 |
+
currentCapListOwner = null;
|
787 |
+
inputBox.focus();
|
788 |
+
|
789 |
+
return;
|
790 |
+
}
|
791 |
+
currentCapListOwner = this; //Got ye now!
|
792 |
+
|
793 |
+
//Move the dropdown near to the button
|
794 |
+
var inputPos = inputBox.offset();
|
795 |
+
list.css({
|
796 |
+
position: 'absolute',
|
797 |
+
left: inputPos.left,
|
798 |
+
top: inputPos.top + inputBox.outerHeight()
|
799 |
+
});
|
800 |
+
|
801 |
+
//Pre-select the current capability (will clear selection if there's no match)
|
802 |
+
list.val(inputBox.val());
|
803 |
+
|
804 |
+
list.show();
|
805 |
+
list.focus();
|
806 |
+
});
|
807 |
+
|
808 |
+
//Also show it when the user presses the down arrow in the input field
|
809 |
+
$('#ws_menu_editor .ws_edit_field-access_level input.ws_field_value').live('keyup', function(event){
|
810 |
+
if ( event.which == 40 ){
|
811 |
+
$(this).parent().find('input.ws_dropdown_button').click();
|
812 |
+
}
|
813 |
+
});
|
814 |
+
|
815 |
+
//Hide capability dropdown when it loses focus
|
816 |
+
capList.blur(function(event){
|
817 |
+
capList.hide();
|
818 |
+
/*
|
819 |
+
* Hackiness : make sure the list doesn't disappear & immediately reappear
|
820 |
+
* when the event that caused it to lose focus was the user clicking on the
|
821 |
+
* dropdown button.
|
822 |
+
*/
|
823 |
+
timeoutForgetOwner = setTimeout(
|
824 |
+
(function(){
|
825 |
+
currentCapListOwner = null;
|
826 |
+
}),
|
827 |
+
200
|
828 |
+
);
|
829 |
+
});
|
830 |
+
|
831 |
+
capList.keydown(function(event){
|
832 |
+
//Also hide it when the user presses Esc
|
833 |
+
if ( event.which == 27 ){
|
834 |
+
var inputBox = $(currentCapListOwner).parent().find('input.ws_field_value');
|
835 |
+
|
836 |
+
capList.hide();
|
837 |
+
if ( currentCapListOwner ){
|
838 |
+
$(currentCapListOwner).parent().find('input.ws_field_value').focus();
|
839 |
+
}
|
840 |
+
currentCapListOwner = null;
|
841 |
+
|
842 |
+
//Select an item & hide the list when the user presses Enter or Tab
|
843 |
+
} else if ( (event.which == 13) || (event.which == 9) ){
|
844 |
+
capList.hide();
|
845 |
+
|
846 |
+
var inputBox = $(currentCapListOwner).parent().find('input.ws_field_value');
|
847 |
+
if ( capList.val() ){
|
848 |
+
inputBox.val(capList.val());
|
849 |
+
inputBox.change();
|
850 |
+
}
|
851 |
+
|
852 |
+
inputBox.focus();
|
853 |
+
currentCapListOwner = null;
|
854 |
+
|
855 |
+
event.preventDefault();
|
856 |
+
}
|
857 |
+
});
|
858 |
+
|
859 |
+
//Eat Tab keys to prevent focus theft. Required to make the "select item on Tab" thing work.
|
860 |
+
capList.keyup(function(event){
|
861 |
+
if ( event.which == 9 ){
|
862 |
+
event.preventDefault();
|
863 |
+
}
|
864 |
+
})
|
865 |
+
|
866 |
+
|
867 |
+
//Update the input & hide the list when an option is clicked
|
868 |
+
capList.click(function(){
|
869 |
+
if ( !currentCapListOwner || !capList.val() ){
|
870 |
+
return;
|
871 |
+
}
|
872 |
+
capList.hide();
|
873 |
+
|
874 |
+
var inputBox = $(currentCapListOwner).parent().find('input.ws_field_value');
|
875 |
+
inputBox.val(capList.val()).change().focus();
|
876 |
+
currentCapListOwner = null;
|
877 |
+
});
|
878 |
+
|
879 |
+
//Highlight an option when the user mouses over it (doesn't work in IE)
|
880 |
+
capList.mousemove(function(event){
|
881 |
+
if ( !event.target ){
|
882 |
+
return;
|
883 |
+
}
|
884 |
+
|
885 |
+
var option = $(event.target);
|
886 |
+
if ( !option.attr('selected') && option.attr('value')){
|
887 |
+
option.attr('selected', 'selected');
|
888 |
+
}
|
889 |
+
});
|
890 |
+
|
891 |
+
|
892 |
+
/*************************************************************************
|
893 |
+
Menu toolbar buttons
|
894 |
+
*************************************************************************/
|
895 |
+
//Show/Hide menu
|
896 |
+
$('#ws_hide_menu').click(function () {
|
897 |
+
//Get the selected menu
|
898 |
+
var selection = $('#ws_menu_box .ws_active');
|
899 |
+
if (!selection.length) return;
|
900 |
+
|
901 |
+
//Mark the menu as hidden/visible
|
902 |
+
//selection.toggleClass('ws_hidden');
|
903 |
+
toggleMenuFlag(selection, 'hidden');
|
904 |
+
|
905 |
+
//Also mark all of it's submenus as hidden/visible
|
906 |
+
if ( menuHasFlag(selection,'hidden') ){
|
907 |
+
$('#' + selection.data('submenu_id') + ' .ws_item').each(function(){
|
908 |
+
addMenuFlag(this, 'hidden');
|
909 |
+
});
|
910 |
+
} else {
|
911 |
+
$('#' + selection.data('submenu_id') + ' .ws_item').each(function(){
|
912 |
+
removeMenuFlag(this, 'hidden');
|
913 |
+
});
|
914 |
+
}
|
915 |
+
});
|
916 |
+
|
917 |
+
//Delete menu
|
918 |
+
$('#ws_delete_menu').click(function () {
|
919 |
+
//Get the selected menu
|
920 |
+
var selection = $('#ws_menu_box .ws_active');
|
921 |
+
if (!selection.length) return;
|
922 |
+
|
923 |
+
if (confirm('Delete this menu?')){
|
924 |
+
//Delete the submenu first
|
925 |
+
$('#' + selection.data('submenu_id')).remove();
|
926 |
+
//Delete the menu
|
927 |
+
selection.remove();
|
928 |
+
}
|
929 |
+
});
|
930 |
+
|
931 |
+
//Copy menu
|
932 |
+
$('#ws_copy_menu').click(function () {
|
933 |
+
//Get the selected menu
|
934 |
+
var selection = $('#ws_menu_box .ws_active');
|
935 |
+
if (!selection.length) return;
|
936 |
+
|
937 |
+
//Store a copy of the current menu state in clipboard
|
938 |
+
menu_in_clipboard = readMenuState(selection);
|
939 |
+
});
|
940 |
+
|
941 |
+
//Cut menu
|
942 |
+
$('#ws_cut_menu').click(function () {
|
943 |
+
//Get the selected menu
|
944 |
+
var selection = $('#ws_menu_box .ws_active');
|
945 |
+
if (!selection.length) return;
|
946 |
+
|
947 |
+
//Store a copy of the current menu state in clipboard
|
948 |
+
menu_in_clipboard = readMenuState(selection);
|
949 |
+
|
950 |
+
//Remove the original menu and submenu
|
951 |
+
selection.remove();
|
952 |
+
$('#'+selection.data('submenu_id')).remove;
|
953 |
+
});
|
954 |
+
|
955 |
+
//Paste menu
|
956 |
+
$('#ws_paste_menu').click(function () {
|
957 |
+
//Check if anything has been copied/cut
|
958 |
+
if (!menu_in_clipboard) return;
|
959 |
+
//Get the selected menu
|
960 |
+
var selection = $('#ws_menu_box .ws_active');
|
961 |
+
|
962 |
+
if (selection.length > 0) {
|
963 |
+
//If a menu is selected add the pasted item after it
|
964 |
+
outputTopMenu(menu_in_clipboard, selection);
|
965 |
+
} else {
|
966 |
+
//Otherwise add the pasted item at the end
|
967 |
+
outputTopMenu(menu_in_clipboard);
|
968 |
+
};
|
969 |
+
});
|
970 |
+
|
971 |
+
//New menu
|
972 |
+
$('#ws_new_menu').click(function () {
|
973 |
+
ws_paste_count++;
|
974 |
+
|
975 |
+
//The new menu starts out rather bare
|
976 |
+
var randomId = 'custom_menu_'+randomMenuId();
|
977 |
+
var menu = {
|
978 |
+
menu_title : null,
|
979 |
+
page_title : null,
|
980 |
+
access_level : null,
|
981 |
+
file : null,
|
982 |
+
css_class : null,
|
983 |
+
icon_url : null,
|
984 |
+
hookname : null,
|
985 |
+
position : null,
|
986 |
+
custom : null,
|
987 |
+
open_in: null,
|
988 |
+
items : {}, //No items
|
989 |
+
defaults : {
|
990 |
+
menu_title : 'Custom Menu '+ws_paste_count,
|
991 |
+
page_title : '',
|
992 |
+
access_level : 'read',
|
993 |
+
file : randomId,
|
994 |
+
css_class : 'menu-top',
|
995 |
+
icon_url : 'images/generic.png',
|
996 |
+
hookname : randomId,
|
997 |
+
open_in : 'same_window',
|
998 |
+
custom: true, //Important : flag the new menu as custom, or it won't show up after saving.
|
999 |
+
position : 0,
|
1000 |
+
separator: false
|
1001 |
+
}
|
1002 |
+
};
|
1003 |
+
|
1004 |
+
//Insert the new menu
|
1005 |
+
result = outputTopMenu(menu);
|
1006 |
+
|
1007 |
+
//The menus's editbox is always open
|
1008 |
+
result.menu.find('.ws_edit_link').click();
|
1009 |
+
});
|
1010 |
+
|
1011 |
+
//New separator
|
1012 |
+
$('#ws_new_separator').click(function () {
|
1013 |
+
ws_paste_count++;
|
1014 |
+
|
1015 |
+
//The new menu starts out rather bare
|
1016 |
+
var randomId = 'separator_'+randomMenuId();
|
1017 |
+
var menu = {
|
1018 |
+
menu_title : null,
|
1019 |
+
page_title : null,
|
1020 |
+
access_level : null,
|
1021 |
+
file : null,
|
1022 |
+
css_class : null,
|
1023 |
+
icon_url : null,
|
1024 |
+
hookname : null,
|
1025 |
+
position : null,
|
1026 |
+
separator : true, //Flag as a separator
|
1027 |
+
custom : null,
|
1028 |
+
open_in : null,
|
1029 |
+
items : {}, //No items
|
1030 |
+
defaults : {
|
1031 |
+
menu_title : '',
|
1032 |
+
page_title : '',
|
1033 |
+
access_level : 'read',
|
1034 |
+
file : randomId,
|
1035 |
+
css_class : 'wp-menu-separator',
|
1036 |
+
icon_url : '',
|
1037 |
+
hookname : '',
|
1038 |
+
position : 0,
|
1039 |
+
custom: false, //Separators don't need to flagged as custom to be retained.
|
1040 |
+
open_in: 'same_window',
|
1041 |
+
separator: true
|
1042 |
+
}
|
1043 |
+
};
|
1044 |
+
|
1045 |
+
//Insert the new menu
|
1046 |
+
result = outputTopMenu(menu);
|
1047 |
+
});
|
1048 |
+
|
1049 |
+
/*************************************************************************
|
1050 |
+
Item toolbar buttons
|
1051 |
+
*************************************************************************/
|
1052 |
+
//Show/Hide item
|
1053 |
+
$('#ws_hide_item').click(function () {
|
1054 |
+
//Get the selected item
|
1055 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
1056 |
+
if (!selection.length) return;
|
1057 |
+
|
1058 |
+
//Mark the item as hidden/visible
|
1059 |
+
toggleMenuFlag(selection, 'hidden');
|
1060 |
+
});
|
1061 |
+
|
1062 |
+
//Delete menu
|
1063 |
+
$('#ws_delete_item').click(function () {
|
1064 |
+
//Get the selected menu
|
1065 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
1066 |
+
if (!selection.length) return;
|
1067 |
+
|
1068 |
+
if (confirm('Delete this menu item?')){
|
1069 |
+
//Delete the item
|
1070 |
+
selection.remove();
|
1071 |
+
}
|
1072 |
+
});
|
1073 |
+
|
1074 |
+
//Copy item
|
1075 |
+
$('#ws_copy_item').click(function () {
|
1076 |
+
//Get the selected item
|
1077 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
1078 |
+
if (!selection.length) return;
|
1079 |
+
|
1080 |
+
//Store a copy of item state in the clipboard
|
1081 |
+
item_in_clipboard = readItemState(selection);
|
1082 |
+
});
|
1083 |
+
|
1084 |
+
//Cut item
|
1085 |
+
$('#ws_cut_item').click(function () {
|
1086 |
+
//Get the selected item
|
1087 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
1088 |
+
if (!selection.length) return;
|
1089 |
+
|
1090 |
+
//Store a copy of item state in the clipboard
|
1091 |
+
item_in_clipboard = readItemState(selection);
|
1092 |
+
|
1093 |
+
//Remove the original item
|
1094 |
+
selection.remove();
|
1095 |
+
});
|
1096 |
+
|
1097 |
+
//Paste item
|
1098 |
+
$('#ws_paste_item').click(function () {
|
1099 |
+
//Check if anything has been copied/cut
|
1100 |
+
if (!item_in_clipboard) return;
|
1101 |
+
//Create a new editor widget for the copied item
|
1102 |
+
var new_item = buildMenuItem(item_in_clipboard);
|
1103 |
+
|
1104 |
+
//Get the selected menu
|
1105 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
1106 |
+
if (selection.length > 0) {
|
1107 |
+
//If an item is selected add the pasted item after it
|
1108 |
+
selection.after(new_item);
|
1109 |
+
} else {
|
1110 |
+
//Otherwise add the pasted item at the end
|
1111 |
+
$('#ws_submenu_box .ws_submenu:visible').append(new_item);
|
1112 |
+
};
|
1113 |
+
|
1114 |
+
new_item.show();
|
1115 |
+
});
|
1116 |
+
|
1117 |
+
//New item
|
1118 |
+
$('#ws_new_item').click(function () {
|
1119 |
+
if ($('.ws_submenu:visible').length < 1) {
|
1120 |
+
return; //Abort if no submenu visible
|
1121 |
+
}
|
1122 |
+
|
1123 |
+
ws_paste_count++;
|
1124 |
+
var entry = {
|
1125 |
+
menu_title : null,
|
1126 |
+
page_title : null,
|
1127 |
+
access_level : null,
|
1128 |
+
file : null,
|
1129 |
+
custom : null,
|
1130 |
+
open_in : null,
|
1131 |
+
defaults : {
|
1132 |
+
menu_title : 'Custom Item ' + ws_paste_count,
|
1133 |
+
page_title : '',
|
1134 |
+
access_level : 'read',
|
1135 |
+
file : 'custom_item_'+randomMenuId(),
|
1136 |
+
is_plugin_page : false,
|
1137 |
+
custom: true,
|
1138 |
+
open_in : 'same_window'
|
1139 |
+
}
|
1140 |
+
};
|
1141 |
+
|
1142 |
+
var menu = buildMenuItem(entry);
|
1143 |
+
|
1144 |
+
//Insert the item into the box
|
1145 |
+
$('#ws_submenu_box .ws_submenu:visible').append(menu);
|
1146 |
+
|
1147 |
+
//The items's editbox is always open
|
1148 |
+
menu.find('.ws_edit_link').click();
|
1149 |
+
});
|
1150 |
+
|
1151 |
+
function compareMenus(a, b){
|
1152 |
+
function jsTrim(str){
|
1153 |
+
return str.replace(/^\s+|\s+$/g, "");
|
1154 |
+
}
|
1155 |
+
|
1156 |
+
var aTitle = jsTrim( $(a).find('.ws_item_title').text() );
|
1157 |
+
var bTitle = jsTrim( $(b).find('.ws_item_title').text() );
|
1158 |
+
|
1159 |
+
aTitle = aTitle.toLowerCase();
|
1160 |
+
bTitle = bTitle.toLowerCase();
|
1161 |
+
|
1162 |
+
return aTitle > bTitle ? 1 : -1;
|
1163 |
+
}
|
1164 |
+
|
1165 |
+
//Sort items in ascending order
|
1166 |
+
$('#ws_sort_ascending').click(function () {
|
1167 |
+
var submenu = $('#ws_submenu_box .ws_submenu:visible');
|
1168 |
+
if (submenu.length < 1) {
|
1169 |
+
return; //Abort if no submenu visible
|
1170 |
+
}
|
1171 |
+
|
1172 |
+
submenu.find('.ws_container').sort(compareMenus);
|
1173 |
+
});
|
1174 |
+
|
1175 |
+
//Sort items in descending order
|
1176 |
+
$('#ws_sort_descending').click(function () {
|
1177 |
+
var submenu = $('#ws_submenu_box .ws_submenu:visible');
|
1178 |
+
if (submenu.length < 1) {
|
1179 |
+
return; //Abort if no submenu visible
|
1180 |
+
}
|
1181 |
+
|
1182 |
+
submenu.find('.ws_container').sort((function(a, b){
|
1183 |
+
return -compareMenus(a, b);
|
1184 |
+
}));
|
1185 |
+
});
|
1186 |
+
|
1187 |
+
//==============================================
|
1188 |
+
// Main buttons
|
1189 |
+
//==============================================
|
1190 |
+
|
1191 |
+
//Save Changes - encode the current menu as JSON and save
|
1192 |
+
$('#ws_save_menu').click(function () {
|
1193 |
+
var data = encodeMenuAsJSON();
|
1194 |
+
$('#ws_data').val(data);
|
1195 |
+
$('#ws_main_form').submit();
|
1196 |
+
});
|
1197 |
+
|
1198 |
+
//Load default menu - load the default WordPress menu
|
1199 |
+
$('#ws_load_menu').click(function () {
|
1200 |
+
if (confirm('Are you sure you want to load the default WordPress menu?')){
|
1201 |
+
outputWpMenu(defaultMenu);
|
1202 |
+
}
|
1203 |
+
});
|
1204 |
+
|
1205 |
+
//Reset menu - re-load the custom menu. Discards any changes made by user.
|
1206 |
+
$('#ws_reset_menu').click(function () {
|
1207 |
+
if (confirm('Undo all changes made in the current editing session?')){
|
1208 |
+
outputWpMenu(customMenu);
|
1209 |
+
}
|
1210 |
+
});
|
1211 |
+
|
1212 |
+
//Export menu - download the current menu as a file
|
1213 |
+
$('#export_dialog').dialog({
|
1214 |
+
autoOpen: false,
|
1215 |
+
closeText: ' ',
|
1216 |
+
modal: true,
|
1217 |
+
minHeight: 100
|
1218 |
+
});
|
1219 |
+
|
1220 |
+
$('#ws_export_menu').click(function(){
|
1221 |
+
var button = $(this);
|
1222 |
+
button.attr('disabled', 'disabled');
|
1223 |
+
button.val('Exporting...');
|
1224 |
+
|
1225 |
+
$('#export_complete_notice, #download_menu_button').hide();
|
1226 |
+
$('#export_progress_notice').show();
|
1227 |
+
$('#export_dialog').dialog('open');
|
1228 |
+
|
1229 |
+
//Encode and store the menu for download
|
1230 |
+
var menu = readMenuTreeState();
|
1231 |
+
var exportData = {
|
1232 |
+
'format' : exportFormatString,
|
1233 |
+
'menu' : menu
|
1234 |
+
};
|
1235 |
+
exportData = $.toJSON(exportData);
|
1236 |
+
|
1237 |
+
$.post(
|
1238 |
+
adminAjaxUrl,
|
1239 |
+
{
|
1240 |
+
'data' : exportData,
|
1241 |
+
'action' : 'export_custom_menu',
|
1242 |
+
'_ajax_nonce' : exportMenuNonce
|
1243 |
+
},
|
1244 |
+
function(data, textStatus){
|
1245 |
+
button.val('Export');
|
1246 |
+
button.removeAttr('disabled');
|
1247 |
+
|
1248 |
+
if ( typeof data['error'] != 'undefined' ){
|
1249 |
+
$('#export_dialog').dialog('close');
|
1250 |
+
alert(data.error);
|
1251 |
+
}
|
1252 |
+
|
1253 |
+
if ( (typeof data['download_url'] != 'undefined') && data.download_url ){
|
1254 |
+
//window.location = data.download_url;
|
1255 |
+
$('#download_menu_button').attr('href', data.download_url);
|
1256 |
+
$('#export_progress_notice').hide();
|
1257 |
+
$('#export_complete_notice, #download_menu_button').show();
|
1258 |
+
}
|
1259 |
+
},
|
1260 |
+
'json'
|
1261 |
+
);
|
1262 |
+
});
|
1263 |
+
|
1264 |
+
$('#ws_cancel_export').click(function(){
|
1265 |
+
$('#export_dialog').dialog('close');
|
1266 |
+
});
|
1267 |
+
|
1268 |
+
$('#download_menu_button').click(function(){
|
1269 |
+
$('#export_dialog').dialog('close');
|
1270 |
+
});
|
1271 |
+
|
1272 |
+
//Import menu - upload an exported menu and show it in the editor
|
1273 |
+
$('#import_dialog').dialog({
|
1274 |
+
autoOpen: false,
|
1275 |
+
closeText: ' ',
|
1276 |
+
modal: true
|
1277 |
+
});
|
1278 |
+
|
1279 |
+
$('#ws_cancel_import').click(function(){
|
1280 |
+
$('#import_dialog').dialog('close');
|
1281 |
+
});
|
1282 |
+
|
1283 |
+
$('#ws_import_menu').click(function(){
|
1284 |
+
$('#import_progress_notice, #import_progress_notice2, #import_complete_notice').hide();
|
1285 |
+
$('#import_menu_form').resetForm();
|
1286 |
+
//The "Upload" button is disabled until the user selects a file
|
1287 |
+
$('#ws_start_import').attr('disabled', 'disabled');
|
1288 |
+
|
1289 |
+
$('#import_dialog .hide-when-uploading').show();
|
1290 |
+
|
1291 |
+
$('#import_dialog').dialog('open');
|
1292 |
+
})
|
1293 |
+
|
1294 |
+
$('#import_file_selector').change(function(){
|
1295 |
+
if ( $(this).val() ){
|
1296 |
+
$('#ws_start_import').removeAttr('disabled');
|
1297 |
+
} else {
|
1298 |
+
$('#ws_start_import').attr('disabled', 'disabled');
|
1299 |
+
};
|
1300 |
+
});
|
1301 |
+
|
1302 |
+
//AJAXify the upload form
|
1303 |
+
$('#import_menu_form').ajaxForm({
|
1304 |
+
dataType : 'json',
|
1305 |
+
beforeSubmit: function(formData, $form, options) {
|
1306 |
+
|
1307 |
+
//Check if the user has selected a file
|
1308 |
+
for(var i = 0; i < formData.length; i++){
|
1309 |
+
if ( formData[i].name == 'menu' ){
|
1310 |
+
if ( (typeof formData[i]['value'] == 'undefined') || !formData[i]['value']){
|
1311 |
+
alert('Select a file first!');
|
1312 |
+
return false;
|
1313 |
+
}
|
1314 |
+
}
|
1315 |
+
}
|
1316 |
+
|
1317 |
+
$('#import_dialog .hide-when-uploading').hide();
|
1318 |
+
$('#import_progress_notice').show();
|
1319 |
+
|
1320 |
+
$('#ws_start_import').attr('disabled', 'disabled');
|
1321 |
+
},
|
1322 |
+
success: function(data){
|
1323 |
+
if ( !$('#import_dialog').dialog('isOpen') ){
|
1324 |
+
//Whoops, the user closed the dialog while the upload was in progress.
|
1325 |
+
//Discard the response silently.
|
1326 |
+
return;
|
1327 |
+
};
|
1328 |
+
|
1329 |
+
if ( typeof data['error'] != 'undefined' ){
|
1330 |
+
alert(data.error);
|
1331 |
+
//Let the user try again
|
1332 |
+
$('#import_menu_form').resetForm();
|
1333 |
+
$('#import_dialog .hide-when-uploading').show();
|
1334 |
+
}
|
1335 |
+
$('#import_progress_notice').hide();
|
1336 |
+
|
1337 |
+
if ( (typeof data['menu'] != 'undefined') && data.menu ){
|
1338 |
+
//Whee, we got back a (seemingly) valid menu. A veritable miracle!
|
1339 |
+
//Lets load it into the editor.
|
1340 |
+
$('#import_progress_notice2').show();
|
1341 |
+
outputWpMenu(data.menu);
|
1342 |
+
$('#import_progress_notice2').hide();
|
1343 |
+
//Display a success notice, then automatically close the window after a few moments
|
1344 |
+
$('#import_complete_notice').show();
|
1345 |
+
setTimeout((function(){
|
1346 |
+
//Close the import dialog
|
1347 |
+
$('#import_dialog').dialog('close');
|
1348 |
+
}), 500);
|
1349 |
+
}
|
1350 |
+
|
1351 |
+
}
|
1352 |
+
});
|
1353 |
+
|
1354 |
+
});
|
1355 |
+
|
1356 |
+
})(jQuery);
|
1357 |
+
|
1358 |
+
//==============================================
|
1359 |
+
// Screen options
|
1360 |
+
//==============================================
|
1361 |
+
|
1362 |
+
jQuery(function($){
|
1363 |
+
var screenOptions = $('#ws-ame-screen-meta-contents');
|
1364 |
+
var checkbox = screenOptions.find('#ws-hide-advanced-settings');
|
1365 |
+
|
1366 |
+
if ( hideAdvancedSettings ){
|
1367 |
+
checkbox.attr('checked', 'checked');
|
1368 |
+
} else {
|
1369 |
+
checkbox.removeAttr('checked');
|
1370 |
+
}
|
1371 |
+
|
1372 |
+
//Update editor state when settings change
|
1373 |
+
checkbox.click(function(){
|
1374 |
+
hideAdvancedSettings = $(this).attr('checked'); //Using '$(this)' instead of 'checkbox' due to jQuery bugs
|
1375 |
+
if ( hideAdvancedSettings ){
|
1376 |
+
$('#ws_menu_editor div.ws_advanced').hide();
|
1377 |
+
$('#ws_menu_editor a.ws_toggle_advanced_fields').text(captionShowAdvanced).show();
|
1378 |
+
} else {
|
1379 |
+
$('#ws_menu_editor div.ws_advanced').show();
|
1380 |
+
$('#ws_menu_editor a.ws_toggle_advanced_fields').text(captionHideAdvanced).hide();
|
1381 |
+
}
|
1382 |
+
|
1383 |
+
$.post(
|
1384 |
+
adminAjaxUrl,
|
1385 |
+
{
|
1386 |
+
'action' : 'ws_ame_save_screen_options',
|
1387 |
+
'hide_advanced_settings' : hideAdvancedSettings?1:0,
|
1388 |
+
'_ajax_nonce' : hideAdvancedSettingsNonce
|
1389 |
+
}
|
1390 |
+
);
|
1391 |
+
});
|
1392 |
+
|
1393 |
+
//Move our options into the screen meta panel
|
1394 |
+
$('#adv-settings').empty().append(screenOptions.show());
|
1395 |
+
});
|
menu-editor-core.php
DELETED
@@ -1,653 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
//Load the "framework"
|
4 |
-
require 'shadow_plugin_framework.php';
|
5 |
-
|
6 |
-
//Load JSON functions for PHP < 5.2
|
7 |
-
if (!class_exists('Services_JSON')){
|
8 |
-
require 'JSON.php';
|
9 |
-
}
|
10 |
-
|
11 |
-
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
12 |
-
|
13 |
-
protected $default_wp_menu = null; //Holds the default WP menu for later use in the editor
|
14 |
-
protected $default_wp_submenu = null; //Holds the default WP menu for later use
|
15 |
-
|
16 |
-
private $blank_menu = null;
|
17 |
-
private $blank_item = null;
|
18 |
-
|
19 |
-
function __construct($plugin_file=''){
|
20 |
-
if ( empty($plugin_file) ) $plugin_file = __FILE__;
|
21 |
-
|
22 |
-
//Determine if the plugin is installed in the mu-plugins directory
|
23 |
-
$this->is_mu_plugin = $this->is_in_wpmu_plugin_dir($plugin_file);
|
24 |
-
//If so, we'll store the custom menu in a site-wide option
|
25 |
-
if ( $this->is_mu_plugin ){
|
26 |
-
$this->sitewide_options = true;
|
27 |
-
}
|
28 |
-
|
29 |
-
//Set some plugin-specific options
|
30 |
-
$this->option_name = 'ws_menu_editor';
|
31 |
-
$this->defaults = array();
|
32 |
-
|
33 |
-
$this->settings_link = 'options-general.php?page=menu_editor';
|
34 |
-
|
35 |
-
$this->magic_hooks = true;
|
36 |
-
$this->magic_hook_priority = 99999;
|
37 |
-
|
38 |
-
//Call the default constructor
|
39 |
-
parent::__construct($plugin_file);
|
40 |
-
|
41 |
-
//Build some template arrays
|
42 |
-
$this->blank_menu = array(
|
43 |
-
'page_title' => null,
|
44 |
-
'menu_title' => null,
|
45 |
-
'access_level' => null,
|
46 |
-
'file' => null,
|
47 |
-
'css_class' => null,
|
48 |
-
'hookname' => null,
|
49 |
-
'icon_url' => null,
|
50 |
-
'position' => null,
|
51 |
-
'defaults' => null,
|
52 |
-
'separator' => null,
|
53 |
-
'custom' => null,
|
54 |
-
);
|
55 |
-
|
56 |
-
$this->blank_item = array(
|
57 |
-
'menu_title' => null,
|
58 |
-
'access_level' => null,
|
59 |
-
'file' => null,
|
60 |
-
'page_title' => null,
|
61 |
-
'position' => null,
|
62 |
-
'custom' => null,
|
63 |
-
);
|
64 |
-
|
65 |
-
}
|
66 |
-
|
67 |
-
//Backwards fompatible json_decode.
|
68 |
-
//We can't define this globally as that conflicts with the version created by Simple Tags.
|
69 |
-
function json_decode($data, $assoc=false){
|
70 |
-
$flag = $assoc?SERVICES_JSON_LOOSE_TYPE:0;
|
71 |
-
$json = new Services_JSON($flag);
|
72 |
-
return( $json->decode($data) );
|
73 |
-
}
|
74 |
-
|
75 |
-
//Backwards fompatible json_encode.
|
76 |
-
//Can't define this globally as that conflicts with Simple Tags.
|
77 |
-
function json_encode($data) {
|
78 |
-
$json = new Services_JSON();
|
79 |
-
return( $json->encode($data) );
|
80 |
-
}
|
81 |
-
|
82 |
-
/**
|
83 |
-
* WPMenuEditor::enqueue_scripts()
|
84 |
-
* Add the JS required by the editor to the page header
|
85 |
-
*
|
86 |
-
* @return void
|
87 |
-
*/
|
88 |
-
function enqueue_scripts(){
|
89 |
-
wp_enqueue_script('jquery');
|
90 |
-
wp_enqueue_script('jquery-ui-sortable');
|
91 |
-
|
92 |
-
//jQuery JSON plugin
|
93 |
-
wp_enqueue_script('jquery-json', $this->plugin_dir_url.'/jquery.json-1.3.js', array('jquery'), '1.3');
|
94 |
-
//Editor's scipts
|
95 |
-
wp_enqueue_script('menu-editor', $this->plugin_dir_url.'/menu-editor.js', array('jquery'));
|
96 |
-
}
|
97 |
-
|
98 |
-
/**
|
99 |
-
* WPMenuEditor::print_editor_css()
|
100 |
-
* Add the editor's CSS file to the page header
|
101 |
-
*
|
102 |
-
* @return void
|
103 |
-
*/
|
104 |
-
function print_editor_css(){
|
105 |
-
echo '<link type="text/css" rel="stylesheet" href="', $this->plugin_dir_url, '/menu-editor.css" />',"\n";
|
106 |
-
}
|
107 |
-
|
108 |
-
/**
|
109 |
-
* WPMenuEditor::hook_admin_menu()
|
110 |
-
* Create a configuration page and load the custom menu
|
111 |
-
*
|
112 |
-
* @return void
|
113 |
-
*/
|
114 |
-
function hook_admin_menu(){
|
115 |
-
global $menu, $submenu;
|
116 |
-
|
117 |
-
//The menu editor is only visible to users with the manage_options privilege.
|
118 |
-
//Or, if the plugin is installed in mu-plugins, only to the site administrator(s).
|
119 |
-
if ( !$this->is_mu_plugin || ( function_exists('is_site_admin') && is_site_admin() ) ){
|
120 |
-
$page = add_options_page('Menu Editor', 'Menu Editor', 'manage_options', 'menu_editor', array(&$this, 'page_menu_editor'));
|
121 |
-
//Output our JS & CSS on that page only
|
122 |
-
add_action("admin_print_scripts-$page", array(&$this, 'enqueue_scripts'));
|
123 |
-
add_action("admin_print_scripts-$page", array(&$this, 'print_editor_css'));
|
124 |
-
}
|
125 |
-
|
126 |
-
$this->default_wp_menu = $menu;
|
127 |
-
$this->default_wp_submenu = $submenu;
|
128 |
-
|
129 |
-
//Is there a custom menu to use?
|
130 |
-
if ( !empty($this->options['custom_menu']) ){
|
131 |
-
//Merge in data from the default menu
|
132 |
-
$tree = $this->menu_merge($this->options['custom_menu'], $menu, $submenu);
|
133 |
-
//Apply the custom menu
|
134 |
-
list($menu, $submenu) = $this->tree2wp($tree);
|
135 |
-
//Save for later - the editor page will need it
|
136 |
-
$this->custom_menu = $tree;
|
137 |
-
//Re-filter the menu (silly WP should do that itself, oh well)
|
138 |
-
$this->filter_menu();
|
139 |
-
}
|
140 |
-
}
|
141 |
-
|
142 |
-
/**
|
143 |
-
* WPMenuEditor::filter_menu()
|
144 |
-
* Loop over the Dashboard submenus and remove pages for which the current user does not have privs.
|
145 |
-
*
|
146 |
-
* @return void
|
147 |
-
*/
|
148 |
-
function filter_menu(){
|
149 |
-
global $menu, $submenu, $_wp_submenu_nopriv, $_wp_menu_nopriv;
|
150 |
-
|
151 |
-
foreach ( array( 'submenu' ) as $sub_loop ) {
|
152 |
-
foreach ($$sub_loop as $parent => $sub) {
|
153 |
-
foreach ($sub as $index => $data) {
|
154 |
-
if ( ! current_user_can($data[1]) ) {
|
155 |
-
unset(${$sub_loop}[$parent][$index]);
|
156 |
-
$_wp_submenu_nopriv[$parent][$data[2]] = true;
|
157 |
-
}
|
158 |
-
}
|
159 |
-
|
160 |
-
if ( empty(${$sub_loop}[$parent]) )
|
161 |
-
unset(${$sub_loop}[$parent]);
|
162 |
-
}
|
163 |
-
}
|
164 |
-
}
|
165 |
-
|
166 |
-
/**
|
167 |
-
* WPMenuEditor::page_menu_editor()
|
168 |
-
* Output the menu editor page
|
169 |
-
*
|
170 |
-
* @return void
|
171 |
-
*/
|
172 |
-
function page_menu_editor(){
|
173 |
-
global $menu, $submenu;
|
174 |
-
if ( !current_user_can('manage_options') ){
|
175 |
-
die("Access denied");
|
176 |
-
}
|
177 |
-
|
178 |
-
//Handle form submissions
|
179 |
-
if (isset($_POST['data'])){
|
180 |
-
check_admin_referer('menu-editor-form');
|
181 |
-
|
182 |
-
//Try to decode a menu tree encoded as JSON
|
183 |
-
$data = $this->json_decode($_POST['data'], true);
|
184 |
-
if (!$data){
|
185 |
-
$fixed = stripslashes($_POST['data']);
|
186 |
-
$data = $this->json_decode( $fixed, true );
|
187 |
-
}
|
188 |
-
|
189 |
-
$url = remove_query_arg('noheader');
|
190 |
-
|
191 |
-
if ($data){
|
192 |
-
//Save the custom menu
|
193 |
-
$this->options['custom_menu'] = $data;
|
194 |
-
$this->save_options();
|
195 |
-
//Redirect back to the editor and display the success message
|
196 |
-
wp_redirect( add_query_arg('message', 1, $url) );
|
197 |
-
} else {
|
198 |
-
//Or redirect & display the error message
|
199 |
-
wp_redirect( add_query_arg('message', 2, $url) );
|
200 |
-
}
|
201 |
-
die();
|
202 |
-
}
|
203 |
-
|
204 |
-
?>
|
205 |
-
<div class="wrap">
|
206 |
-
<h2>Menu Editor</h2>
|
207 |
-
<?php
|
208 |
-
|
209 |
-
if ( !empty($_GET['message']) ){
|
210 |
-
if ( intval($_GET['message']) == 1 ){
|
211 |
-
echo '<div id="message" class="updated fade"><p><strong>Settings saved.</strong></p></div>';
|
212 |
-
} elseif ( intval($_GET['message']) == 2 ) {
|
213 |
-
echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
|
214 |
-
}
|
215 |
-
}
|
216 |
-
|
217 |
-
//Build a tree struct. for the default menu
|
218 |
-
$default_menu = $this->wp2tree($this->default_wp_menu, $this->default_wp_submenu);
|
219 |
-
|
220 |
-
//Is there a custom menu?
|
221 |
-
if (!empty($this->options['custom_menu'])){
|
222 |
-
$custom_menu = $this->options['custom_menu'];
|
223 |
-
//Merge in the current defaults
|
224 |
-
$custom_menu = $this->menu_merge($custom_menu, $this->default_wp_menu, $this->default_wp_submenu);
|
225 |
-
} else {
|
226 |
-
//Start out with the default menu if there is no user-created one
|
227 |
-
$custom_menu = $default_menu;
|
228 |
-
}
|
229 |
-
|
230 |
-
//Encode both menus as JSON
|
231 |
-
$default_menu_js = $this->getMenuAsJS($default_menu);
|
232 |
-
$custom_menu_js = $this->getMenuAsJS($custom_menu);
|
233 |
-
|
234 |
-
$plugin_url = $this->plugin_dir_url;
|
235 |
-
$images_url = $this->plugin_dir_url . '/images';
|
236 |
-
?>
|
237 |
-
<div id='ws_menu_editor'>
|
238 |
-
<div id='ws_menu_box' class='ws_main_container'>
|
239 |
-
<div class='ws_toolbar'>
|
240 |
-
<a id='ws_cut_menu' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' /></a>
|
241 |
-
<a id='ws_copy_menu' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $images_url; ?>/page_white_copy.png' /></a>
|
242 |
-
<a id='ws_paste_menu' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $images_url; ?>/page_white_paste.png' /></a>
|
243 |
-
<a id='ws_new_menu' class='ws_button' href='javascript:void(0)' title='New'><img src='<?php echo $images_url; ?>/page_white_add.png' /></a>
|
244 |
-
<a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $images_url; ?>/plugin_disabled.png' /></a>
|
245 |
-
<a id='ws_delete_menu' class='ws_button' href='javascript:void(0)' title='Delete'><img src='<?php echo $images_url; ?>/page_white_delete.png' /></a>
|
246 |
-
</div>
|
247 |
-
</div>
|
248 |
-
<div id='ws_submenu_box' class='ws_main_container'>
|
249 |
-
<div class='ws_toolbar'>
|
250 |
-
<a id='ws_cut_item' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' /></a>
|
251 |
-
<a id='ws_copy_item' class='ws_button' href='javascript:void(0)' title='Copy'><img src='<?php echo $images_url; ?>/page_white_copy.png' /></a>
|
252 |
-
<a id='ws_paste_item' class='ws_button' href='javascript:void(0)' title='Paste'><img src='<?php echo $images_url; ?>/page_white_paste.png' /></a>
|
253 |
-
<a id='ws_new_item' class='ws_button' href='javascript:void(0)' title='New'><img src='<?php echo $images_url; ?>/page_white_add.png' /></a>
|
254 |
-
<a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $images_url; ?>/plugin_disabled.png' /></a>
|
255 |
-
<a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete'><img src='<?php echo $images_url; ?>/page_white_delete.png' /></a>
|
256 |
-
</div>
|
257 |
-
</div>
|
258 |
-
</div>
|
259 |
-
|
260 |
-
<form method="post" action="<?php echo admin_url('options-general.php?page=menu_editor&noheader=1'); ?>" id='ws_main_form' name='ws_main_form'>
|
261 |
-
<div class="ws_main_container" style="width: 138px;">
|
262 |
-
<?php wp_nonce_field('menu-editor-form'); ?>
|
263 |
-
<input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes"
|
264 |
-
style="margin-bottom: 20px;" />
|
265 |
-
<input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
|
266 |
-
<input type="button" id='ws_reset_menu' value="Reset menu" class="button ws_main_button" />
|
267 |
-
<input type="hidden" name="data" id="ws_data" value="">
|
268 |
-
</div>
|
269 |
-
</form>
|
270 |
-
|
271 |
-
</div>
|
272 |
-
<script type='text/javascript'>
|
273 |
-
|
274 |
-
var defaultMenu = <?php echo $default_menu_js; ?>;
|
275 |
-
var customMenu = <?php echo $custom_menu_js; ?>;
|
276 |
-
|
277 |
-
</script>
|
278 |
-
<?php
|
279 |
-
}
|
280 |
-
|
281 |
-
/**
|
282 |
-
* WPMenuEditor::getMenuAsJS()
|
283 |
-
* Encode a menu tree as JSON
|
284 |
-
*
|
285 |
-
* @param array $tree
|
286 |
-
* @return string
|
287 |
-
*/
|
288 |
-
function getMenuAsJS($tree){
|
289 |
-
return $this->json_encode($tree);
|
290 |
-
}
|
291 |
-
|
292 |
-
/**
|
293 |
-
* WPMenuEditor::menu2assoc()
|
294 |
-
* Convert a WP menu structure to an associative array
|
295 |
-
*
|
296 |
-
* @param array $item An element of the $menu array
|
297 |
-
* @param integer $pos The position (index) of the menu item
|
298 |
-
* @return array
|
299 |
-
*/
|
300 |
-
function menu2assoc($item, $pos=0){
|
301 |
-
$item = array(
|
302 |
-
'page_title' => $item[3],
|
303 |
-
'menu_title' => $item[0],
|
304 |
-
'access_level' => $item[1],
|
305 |
-
'file' => $item[2],
|
306 |
-
'css_class' => $item[4],
|
307 |
-
'hookname' => (isset($item[5])?$item[5]:''), //ID
|
308 |
-
'icon_url' => (isset($item[6])?$item[6]:''),
|
309 |
-
'position' => $pos,
|
310 |
-
);
|
311 |
-
return $item;
|
312 |
-
}
|
313 |
-
|
314 |
-
/**
|
315 |
-
* WPMenuEditor::submenu2assoc()
|
316 |
-
* Converts a WP submenu structure to an associative array
|
317 |
-
*
|
318 |
-
* @param array $item An element of the $submenu array
|
319 |
-
* @param integer $pos The position (index) of that element
|
320 |
-
* @return
|
321 |
-
*/
|
322 |
-
function submenu2assoc($item, $pos=0){
|
323 |
-
$item = array(
|
324 |
-
'menu_title' => $item[0],
|
325 |
-
'access_level' => $item[1],
|
326 |
-
'file' => $item[2],
|
327 |
-
'page_title' => (isset($item[3])?$item[3]:''),
|
328 |
-
'position' => $pos,
|
329 |
-
);
|
330 |
-
return $item;
|
331 |
-
}
|
332 |
-
|
333 |
-
/**
|
334 |
-
* WPMenuEditor::build_lookups()
|
335 |
-
* Populate lookup arrays with default values from $menu and $submenu. Used later to merge
|
336 |
-
* a custom menu with the native WordPress menu structure somewhat gracefully.
|
337 |
-
*
|
338 |
-
* @param array $menu
|
339 |
-
* @param array $submenu
|
340 |
-
* @return array An array with two elements containing menu and submenu defaults.
|
341 |
-
*/
|
342 |
-
function build_lookups($menu, $submenu){
|
343 |
-
//Process the top menu
|
344 |
-
$menu_defaults = array();
|
345 |
-
foreach($menu as $pos => $item){
|
346 |
-
$item = $this->menu2assoc($item, $pos);
|
347 |
-
if ($item['file'] != '') { //skip separators (empty menus)
|
348 |
-
$menu_defaults[$item['file']] = $item; //index by filename
|
349 |
-
}
|
350 |
-
}
|
351 |
-
|
352 |
-
//Process the submenu
|
353 |
-
$submenu_defaults = array();
|
354 |
-
foreach($submenu as $parent => $items){
|
355 |
-
foreach($items as $pos => $item){
|
356 |
-
$item = $this->submenu2assoc($item, $pos);
|
357 |
-
//save the default parent menu
|
358 |
-
$item['parent'] = $parent;
|
359 |
-
$submenu_defaults[$item['file']] = $item; //index by filename
|
360 |
-
}
|
361 |
-
}
|
362 |
-
|
363 |
-
return array($menu_defaults, $submenu_defaults);
|
364 |
-
}
|
365 |
-
|
366 |
-
/**
|
367 |
-
* WPMenuEditor::menu_merge()
|
368 |
-
* Merge $menu and $submenu into the $tree. Adds/replaces defaults, inserts new items
|
369 |
-
* and marks missing items as such.
|
370 |
-
*
|
371 |
-
* @param array $tree A menu in plugin's internal form
|
372 |
-
* @param array $menu WordPress menu structure
|
373 |
-
* @param array $submenu WordPress submenu structure
|
374 |
-
* @return array Updated menu tree
|
375 |
-
*/
|
376 |
-
function menu_merge($tree, $menu, $submenu){
|
377 |
-
list($menu_defaults, $submenu_defaults) = $this->build_lookups($menu, $submenu);
|
378 |
-
|
379 |
-
//Iterate over all menus and submenus and look up default values
|
380 |
-
foreach ($tree as $topfile => &$topmenu){
|
381 |
-
|
382 |
-
//Is this menu present in the default WP menu?
|
383 |
-
if (isset($menu_defaults[$topfile])){
|
384 |
-
//Yes, load defaults from that item
|
385 |
-
$topmenu['defaults'] = $menu_defaults[$topfile];
|
386 |
-
//Note that the original item was used
|
387 |
-
$menu_defaults[$topfile]['used'] = true;
|
388 |
-
} else {
|
389 |
-
//Record the menu as missing, unless it's a menu separator
|
390 |
-
if ( empty($topmenu['separator']) )
|
391 |
-
$topmenu['missing'] = true;
|
392 |
-
}
|
393 |
-
|
394 |
-
if (is_array($topmenu['items'])) {
|
395 |
-
//Iterate over submenu items
|
396 |
-
foreach ($topmenu['items'] as $file => &$item){
|
397 |
-
//Is this item present in the default WP menu?
|
398 |
-
if (isset($submenu_defaults[$file])){
|
399 |
-
//Yes, load defaults from that item
|
400 |
-
$item['defaults'] = $submenu_defaults[$file];
|
401 |
-
$submenu_defaults[$file]['used'] = true;
|
402 |
-
} else {
|
403 |
-
//Record as missing
|
404 |
-
$item['missing'] = true;
|
405 |
-
}
|
406 |
-
}
|
407 |
-
}
|
408 |
-
}
|
409 |
-
|
410 |
-
//If we don't unset these they will fuck up the next two loops where the same names are used.
|
411 |
-
unset($topmenu);
|
412 |
-
unset($item);
|
413 |
-
|
414 |
-
//Note : Now we have some items marked as missing, and some items in lookup arrays
|
415 |
-
//that are not marked as used. The missing items are handled elsewhere (e.g. tree2wp()),
|
416 |
-
//but lets merge in the unused items now.
|
417 |
-
|
418 |
-
//Find and merge unused toplevel menus
|
419 |
-
foreach ($menu_defaults as $topfile => $topmenu){
|
420 |
-
if ( !empty($topmenu['used']) ) continue;
|
421 |
-
|
422 |
-
//Found an unused item. Build the tree entry.
|
423 |
-
$entry = $this->blank_menu;
|
424 |
-
$entry['defaults'] = $topmenu;
|
425 |
-
$entry['items'] = array(); //prepare a place for menu items, if any.
|
426 |
-
//Note that this item is unused
|
427 |
-
$entry['unused'] = true;
|
428 |
-
//Add the new entry to the menu tree
|
429 |
-
$tree[$topfile] = $entry;
|
430 |
-
}
|
431 |
-
unset($topmenu);
|
432 |
-
|
433 |
-
//Find and merge submenu items
|
434 |
-
foreach($submenu_defaults as $file => $item){
|
435 |
-
if ( !empty($item['used']) ) continue;
|
436 |
-
//Found an unused item. Build an entry and attach it under the default toplevel menu.
|
437 |
-
$entry = $this->blank_item;
|
438 |
-
$entry['defaults'] = $item;
|
439 |
-
//Note that this item is unused
|
440 |
-
$entry['unused'] = true;
|
441 |
-
|
442 |
-
//Check if the toplevel menu exists
|
443 |
-
if (isset($tree[$item['parent']])) {
|
444 |
-
//Okay, insert the item.
|
445 |
-
$tree[$item['parent']]['items'][$item['file']] = $entry;
|
446 |
-
} else {
|
447 |
-
//Ooops? This should never happen. Some kind of inconsistency?
|
448 |
-
}
|
449 |
-
}
|
450 |
-
|
451 |
-
//Resort the tree to ensure the found items are in the right spots
|
452 |
-
$tree = $this->sort_menu_tree($tree);
|
453 |
-
|
454 |
-
return $tree;
|
455 |
-
}
|
456 |
-
|
457 |
-
/**
|
458 |
-
* WPMenuEditor::wp2tree()
|
459 |
-
* Convert the WP menu structure to the internal representation. All properties set as defaults.
|
460 |
-
*
|
461 |
-
* @param array $menu
|
462 |
-
* @param array $submenu
|
463 |
-
* @return array
|
464 |
-
*/
|
465 |
-
function wp2tree($menu, $submenu){
|
466 |
-
$tree = array();
|
467 |
-
$separator_count = 0;
|
468 |
-
foreach ($menu as $pos => $item){
|
469 |
-
|
470 |
-
$tree_item = $this->blank_menu;
|
471 |
-
$tree_item['defaults'] = $this->menu2assoc($item, $pos);
|
472 |
-
$tree_item['separator'] = empty($item[2]) || empty($item[0]);
|
473 |
-
|
474 |
-
$item_file = $tree_item['defaults']['file'];
|
475 |
-
if ( empty($item_file) ){
|
476 |
-
$item_file = 'separator_'.$separator_count.'_';
|
477 |
-
$separator_count++;
|
478 |
-
}
|
479 |
-
|
480 |
-
$tree[$item_file] = $tree_item;
|
481 |
-
}
|
482 |
-
|
483 |
-
//Attach all submenu items
|
484 |
-
foreach($submenu as $parent=>$items){
|
485 |
-
//Skip items that belong to a non-existent parent menu.
|
486 |
-
//Rationale : All In One SEO Pack 1.6.10 (and possibly others) doth add such invalid submenus.
|
487 |
-
if ( !isset($tree[$parent]) ) continue;
|
488 |
-
|
489 |
-
foreach($items as $pos=>$item){
|
490 |
-
//Add this item under the parent
|
491 |
-
$tree[$parent]['items'][$item[2]] = array(
|
492 |
-
'menu_title' => null,
|
493 |
-
'access_level' => null,
|
494 |
-
'file' => null,
|
495 |
-
'page_title' => null,
|
496 |
-
'position' => null,
|
497 |
-
'defaults' => $this->submenu2assoc($item, $pos),
|
498 |
-
);
|
499 |
-
}
|
500 |
-
}
|
501 |
-
|
502 |
-
$tree = $this->sort_menu_tree($tree);
|
503 |
-
|
504 |
-
return $tree;
|
505 |
-
}
|
506 |
-
|
507 |
-
/**
|
508 |
-
* WPMenuEditor::apply_defaults()
|
509 |
-
* Sets all undefined fields to the default value
|
510 |
-
*
|
511 |
-
* @param array $item Menu item in the plugin's internal form
|
512 |
-
* @return array
|
513 |
-
*/
|
514 |
-
function apply_defaults($item){
|
515 |
-
foreach($item as $key => $value){
|
516 |
-
//Is the field set?
|
517 |
-
if ($value === null){
|
518 |
-
//Use default, if available
|
519 |
-
if (isset($item['defaults']) && isset($item['defaults'][$key])){
|
520 |
-
$item[$key] = $item['defaults'][$key];
|
521 |
-
}
|
522 |
-
}
|
523 |
-
}
|
524 |
-
return $item;
|
525 |
-
}
|
526 |
-
|
527 |
-
/**
|
528 |
-
* WPMenuEditor::compare_position()
|
529 |
-
* Custom comparison function that compares menu items based on their position in the menu.
|
530 |
-
*
|
531 |
-
* @param array $a
|
532 |
-
* @param array $b
|
533 |
-
* @return int
|
534 |
-
*/
|
535 |
-
function compare_position($a, $b){
|
536 |
-
if ($a['position']!==null) {
|
537 |
-
$p1 = $a['position'];
|
538 |
-
} else {
|
539 |
-
if ( isset($a['defaults']['position']) ){
|
540 |
-
$p1 = $a['defaults']['position'];
|
541 |
-
} else {
|
542 |
-
$p1 = 0;
|
543 |
-
}
|
544 |
-
}
|
545 |
-
|
546 |
-
if ($b['position']!==null) {
|
547 |
-
$p2 = $b['position'];
|
548 |
-
} else {
|
549 |
-
if ( isset($b['defaults']['position']) ){
|
550 |
-
$p2 = $b['defaults']['position'];
|
551 |
-
} else {
|
552 |
-
$p2 = 0;
|
553 |
-
}
|
554 |
-
}
|
555 |
-
|
556 |
-
return $p1 - $p2;
|
557 |
-
}
|
558 |
-
|
559 |
-
/**
|
560 |
-
* WPMenuEditor::sort_menu_tree()
|
561 |
-
* Sort the menus and menu items of a given menu according to their positions
|
562 |
-
*
|
563 |
-
* @param array $tree A menu structure in the internal format
|
564 |
-
* @return array Sorted menu in the internal format
|
565 |
-
*/
|
566 |
-
function sort_menu_tree($tree){
|
567 |
-
//Resort the tree to ensure the found items are in the right spots
|
568 |
-
uasort($tree, array(&$this, 'compare_position'));
|
569 |
-
//Resort all submenus as well
|
570 |
-
foreach ($tree as $topfile => &$topmenu){
|
571 |
-
if (!empty($topmenu['items'])){
|
572 |
-
uasort($topmenu['items'], array(&$this, 'compare_position'));
|
573 |
-
}
|
574 |
-
}
|
575 |
-
|
576 |
-
return $tree;
|
577 |
-
}
|
578 |
-
|
579 |
-
/**
|
580 |
-
* WPMenuEditor::tree2wp()
|
581 |
-
* Convert internal menu representation to the form used by WP.
|
582 |
-
*
|
583 |
-
* @param array $tree
|
584 |
-
* @return array $menu and $submenu
|
585 |
-
*/
|
586 |
-
function tree2wp($tree){
|
587 |
-
$menu = array();
|
588 |
-
$submenu = array();
|
589 |
-
|
590 |
-
//Sort the menu by position
|
591 |
-
uasort($tree, array(&$this, 'compare_position'));
|
592 |
-
|
593 |
-
//Prepare the top menu
|
594 |
-
foreach ($tree as &$topmenu){
|
595 |
-
|
596 |
-
//Skip missing menus, unless they're user-created and thus might point to a non-standard file
|
597 |
-
if ( !empty($topmenu['missing']) && empty($topmenu['custom']) ) continue;
|
598 |
-
//Skip hidden entries
|
599 |
-
if (!empty($topmenu['hidden'])) continue;
|
600 |
-
|
601 |
-
//Build the WP item structure, using defaults where necessary
|
602 |
-
$topmenu = $this->apply_defaults($topmenu);
|
603 |
-
$menu[] = array(
|
604 |
-
$topmenu['menu_title'],
|
605 |
-
$topmenu['access_level'],
|
606 |
-
$topmenu['file'],
|
607 |
-
$topmenu['page_title'],
|
608 |
-
$topmenu['css_class'],
|
609 |
-
$topmenu['hookname'], //ID
|
610 |
-
$topmenu['icon_url']
|
611 |
-
);
|
612 |
-
|
613 |
-
//Prepare the submenu of this menu
|
614 |
-
if( !empty($topmenu['items']) ){
|
615 |
-
$items = $topmenu['items'];
|
616 |
-
//Sort by position
|
617 |
-
uasort($items, array(&$this, 'compare_position'));
|
618 |
-
foreach ($items as $item) {
|
619 |
-
|
620 |
-
//Skip missing items, unless they're user-created
|
621 |
-
if ( !empty($item['missing']) && empty($item['custom']) ) continue;
|
622 |
-
//Skip hidden items
|
623 |
-
if (!empty($item['hidden'])) {
|
624 |
-
continue;
|
625 |
-
}
|
626 |
-
|
627 |
-
$item = $this->apply_defaults($item);
|
628 |
-
$submenu[$topmenu['file']][] = array(
|
629 |
-
$item['menu_title'],
|
630 |
-
$item['access_level'],
|
631 |
-
$item['file'],
|
632 |
-
$item['page_title'],
|
633 |
-
);
|
634 |
-
}
|
635 |
-
}
|
636 |
-
}
|
637 |
-
|
638 |
-
return array($menu, $submenu);
|
639 |
-
}
|
640 |
-
|
641 |
-
/**
|
642 |
-
* WPMenuEditor::is_wp27()
|
643 |
-
* Check if running WordPress 2.7 or later (unused)
|
644 |
-
*
|
645 |
-
* @return bool
|
646 |
-
*/
|
647 |
-
function is_wp27(){
|
648 |
-
global $wp_version;
|
649 |
-
return function_exists('register_uninstall_hook');
|
650 |
-
}
|
651 |
-
} //class
|
652 |
-
|
653 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
menu-editor.css
DELETED
@@ -1,177 +0,0 @@
|
|
1 |
-
/* Admin Menu Editor CSS file */
|
2 |
-
|
3 |
-
.ws_main_container {
|
4 |
-
margin: 2px;
|
5 |
-
width: 302px;
|
6 |
-
min-height: 30px;
|
7 |
-
float: left;
|
8 |
-
display:block;
|
9 |
-
border: 1px solid #cdd5d5;
|
10 |
-
padding: 4px;
|
11 |
-
}
|
12 |
-
|
13 |
-
.ws_main_button {
|
14 |
-
clear: both;
|
15 |
-
display: block;
|
16 |
-
margin: 4px;
|
17 |
-
margin-left: auto;
|
18 |
-
margin-right: auto;
|
19 |
-
width: 120px;
|
20 |
-
padding: 4px !important;
|
21 |
-
}
|
22 |
-
|
23 |
-
.ws_container {
|
24 |
-
display: block;
|
25 |
-
width: 290px;
|
26 |
-
padding : 3px;
|
27 |
-
margin: 2px;
|
28 |
-
border: 1px solid #a9badb;
|
29 |
-
background-color: #bdd3ff;
|
30 |
-
}
|
31 |
-
|
32 |
-
.ws_active {
|
33 |
-
background-color : #8eb0f1 !important; /* make sure this overrides ws_menu_separator */
|
34 |
-
}
|
35 |
-
|
36 |
-
#ws_menu_box {
|
37 |
-
}
|
38 |
-
|
39 |
-
#ws_submenu_box {
|
40 |
-
}
|
41 |
-
|
42 |
-
.ws_menu {
|
43 |
-
}
|
44 |
-
|
45 |
-
.ws_menu_separator {
|
46 |
-
background-color: #f0f0f0;
|
47 |
-
}
|
48 |
-
|
49 |
-
.ws_submenu {
|
50 |
-
background-color: #f0fafa;
|
51 |
-
min-height: 2em;
|
52 |
-
}
|
53 |
-
|
54 |
-
|
55 |
-
.ws_item_head {
|
56 |
-
padding-left: 2px;
|
57 |
-
padding-top: 2px;
|
58 |
-
padding-bottom: 2px;
|
59 |
-
cursor: default;
|
60 |
-
}
|
61 |
-
|
62 |
-
.ws_item_title {
|
63 |
-
}
|
64 |
-
|
65 |
-
.ws_edit_link {
|
66 |
-
float: right;
|
67 |
-
margin-right: 0px;
|
68 |
-
cursor: pointer;
|
69 |
-
display:block;
|
70 |
-
height: 18px;
|
71 |
-
width: 40px;
|
72 |
-
background-image: url('images/bullet_arrow_down2.png');
|
73 |
-
background-repeat: no-repeat;
|
74 |
-
background-position: center;
|
75 |
-
}
|
76 |
-
a.ws_edit_link:hover {
|
77 |
-
background-color: #ffffd0;
|
78 |
-
background-image: url('images/bullet_arrow_down2.png');
|
79 |
-
background-repeat: no-repeat;
|
80 |
-
background-position: center;
|
81 |
-
}
|
82 |
-
|
83 |
-
.ws_edit_link_expanded {
|
84 |
-
background-color: #ffffd0;
|
85 |
-
border-bottom: none;
|
86 |
-
border-color: #ffffd0;
|
87 |
-
background-image: url('images/bullet_arrow_down2.png');
|
88 |
-
background-repeat: no-repeat;
|
89 |
-
background-position: center;
|
90 |
-
}
|
91 |
-
|
92 |
-
/* Menu/menu item flags */
|
93 |
-
.ws_flag_container {
|
94 |
-
float: right;
|
95 |
-
margin-right: 4px;
|
96 |
-
}
|
97 |
-
|
98 |
-
.ws_flag {
|
99 |
-
display: block;
|
100 |
-
float: right;
|
101 |
-
width: 16px;
|
102 |
-
height: 16px;
|
103 |
-
margin-left: 4px;
|
104 |
-
background-repeat: no-repeat;
|
105 |
-
}
|
106 |
-
|
107 |
-
/* user-created items */
|
108 |
-
.ws_custom_item_flag {
|
109 |
-
background-image: url('images/page_white_add.png');
|
110 |
-
}
|
111 |
-
|
112 |
-
/* items not present in the default menu */
|
113 |
-
.ws_missing_flag {
|
114 |
-
background-image: url('images/plugin_error.png');
|
115 |
-
}
|
116 |
-
|
117 |
-
/* unused items - those that are in the default menu but not in the custom one */
|
118 |
-
.ws_unused_flag {
|
119 |
-
background-image: url('images/plugin_add.png');
|
120 |
-
}
|
121 |
-
|
122 |
-
/* hidden items */
|
123 |
-
.ws_hidden_flag {
|
124 |
-
background-image: url('images/plugin_disabled.png');
|
125 |
-
}
|
126 |
-
|
127 |
-
/* These classes could be used to apply different styles to items depending on their flags */
|
128 |
-
.ws_missing { }
|
129 |
-
.ws_custom_item { }
|
130 |
-
.ws_hidden { }
|
131 |
-
.ws_unused { }
|
132 |
-
|
133 |
-
|
134 |
-
.ws_item { }
|
135 |
-
|
136 |
-
.ws_editbox {
|
137 |
-
display: block;
|
138 |
-
background-color: #ffffd0;
|
139 |
-
padding: 4px;
|
140 |
-
}
|
141 |
-
|
142 |
-
/* the reset-to-default button */
|
143 |
-
.ws_reset_button {
|
144 |
-
font-size: smaller;
|
145 |
-
margin : 1px;
|
146 |
-
cursor: pointer;
|
147 |
-
}
|
148 |
-
|
149 |
-
.ws_editbox input[type="text"] {
|
150 |
-
width: 220px;
|
151 |
-
}
|
152 |
-
|
153 |
-
.ws_input_default {
|
154 |
-
color: gray;
|
155 |
-
}
|
156 |
-
|
157 |
-
.ws_edit_field {
|
158 |
-
margin-bottom: 8px;
|
159 |
-
}
|
160 |
-
|
161 |
-
.ws_toolbar {
|
162 |
-
display: block;
|
163 |
-
width: 290px;
|
164 |
-
height: 32px;
|
165 |
-
}
|
166 |
-
|
167 |
-
.ws_button {
|
168 |
-
display: block;
|
169 |
-
margin: 2px;
|
170 |
-
padding: 4px;
|
171 |
-
border: 1px solid #c0c0e0;
|
172 |
-
float: left;
|
173 |
-
}
|
174 |
-
|
175 |
-
a.ws_button:hover {
|
176 |
-
background-color: #d0e0ff;
|
177 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
menu-editor.js
DELETED
@@ -1,690 +0,0 @@
|
|
1 |
-
//(c) W-Shadow
|
2 |
-
|
3 |
-
function escapeJS (s) {
|
4 |
-
s = s + '';
|
5 |
-
return s.replace(/&/g,'&').replace(/>/g,'>').replace(/</g,'<').
|
6 |
-
replace(/"/g,'"').replace(/'/g,"'").replace(/\\/g,'\');
|
7 |
-
};
|
8 |
-
|
9 |
-
(function ($){
|
10 |
-
|
11 |
-
function outputWpMenu(menu){
|
12 |
-
//Remove the current menu data
|
13 |
-
$('.ws_container').remove();
|
14 |
-
$('.ws_submenu').remove();
|
15 |
-
|
16 |
-
//Display the new menu
|
17 |
-
var i = 0;
|
18 |
-
for (var filename in menu){
|
19 |
-
outputTopMenu(menu[filename], filename, i);
|
20 |
-
i++;
|
21 |
-
}
|
22 |
-
|
23 |
-
//Make the submenus sortable
|
24 |
-
$('.ws_submenu').sortable({
|
25 |
-
items: '> .ws_container',
|
26 |
-
cursor: 'move',
|
27 |
-
dropOnEmpty: true,
|
28 |
-
});
|
29 |
-
|
30 |
-
//Highlight the clicked menu item and show it's submenu
|
31 |
-
$('.ws_item_head').click(function () {
|
32 |
-
var p = $(this).parent();
|
33 |
-
//Highlight the active item
|
34 |
-
p.siblings().removeClass('ws_active');
|
35 |
-
p.addClass('ws_active');
|
36 |
-
//Show the appropriate submenu
|
37 |
-
if (p.hasClass('ws_menu')) {
|
38 |
-
$('.ws_submenu:visible').hide();
|
39 |
-
$('#'+p.attr('submenu_id')).show();
|
40 |
-
}
|
41 |
-
});
|
42 |
-
|
43 |
-
//Expand/collapse a menu item
|
44 |
-
$('.ws_edit_link').click(function () {
|
45 |
-
var box = $(this).parent().parent().find('.ws_editbox');
|
46 |
-
$(this).toggleClass('ws_edit_link_expanded');
|
47 |
-
//show/hide the editbox
|
48 |
-
if ($(this).hasClass('ws_edit_link_expanded')){
|
49 |
-
box.show();
|
50 |
-
} else {
|
51 |
-
//Make sure changes are applied before the menu is collapsed
|
52 |
-
box.find('input').change();
|
53 |
-
box.hide();
|
54 |
-
}
|
55 |
-
});
|
56 |
-
|
57 |
-
//The "Default" button : Reset to default value when clicked
|
58 |
-
$('.ws_reset_button').click(function () {
|
59 |
-
//Find the related input field
|
60 |
-
var field = $(this).siblings('input');
|
61 |
-
if (field.length > 0) {
|
62 |
-
//Set the value to the default
|
63 |
-
field.val(field.attr('default'));
|
64 |
-
field.addClass('ws_input_default');
|
65 |
-
//Trigget the change event to ensure consistency
|
66 |
-
field.change();
|
67 |
-
}
|
68 |
-
});
|
69 |
-
|
70 |
-
//When a field is edited, change it's appearance if it's contents don't match the default value.
|
71 |
-
$('.ws_edit_field input[type="text"]').change(function () {
|
72 |
-
if ( $(this).attr('default') != $(this).val() ) {
|
73 |
-
$(this).removeClass('ws_input_default');
|
74 |
-
}
|
75 |
-
|
76 |
-
//If the changed field is the menu title, update the header
|
77 |
-
if ( $(this).parent().attr('field_name')=='menu_title' ){
|
78 |
-
$(this).parent().parent().parent().find('.ws_item_title').html($(this).val()+' ');
|
79 |
-
}
|
80 |
-
});
|
81 |
-
|
82 |
-
//When the "Custom" checkbox is clicked, add/remove the ws_custom_item class to
|
83 |
-
//the menu (or menu item) in question.
|
84 |
-
$('.ws_custom_toggle').change(function(){
|
85 |
-
//Find the container
|
86 |
-
var my_container = $(this).parents('.ws_container:first');
|
87 |
-
if ( $(this).is(':checked') ){
|
88 |
-
addMenuFlag(my_container, 'custom_item');
|
89 |
-
} else {
|
90 |
-
removeMenuFlag(my_container, 'custom_item');
|
91 |
-
}
|
92 |
-
});
|
93 |
-
}
|
94 |
-
|
95 |
-
function outputTopMenu(menu, filename, ind){
|
96 |
-
id = 'topmenu-'+ind;
|
97 |
-
submenu_id = 'submenu-'+ind;
|
98 |
-
|
99 |
-
var subclass = '';
|
100 |
-
if ( menu.separator ) {
|
101 |
-
subclass = subclass + ' ws_menu_separator';
|
102 |
-
}
|
103 |
-
|
104 |
-
//Create the menu HTML
|
105 |
-
var s = '<div id="'+id+'" class="ws_container ws_menu '+subclass+'" submenu_id="'+submenu_id+'">'+
|
106 |
-
'<div class="ws_item_head">'+
|
107 |
-
'<a class="ws_edit_link"> </a>'+
|
108 |
-
'<div class="ws_flag_container"> </div>'+
|
109 |
-
'<span class="ws_item_title">'+
|
110 |
-
((menu.menu_title!=null)?menu.menu_title:menu.defaults.menu_title)+
|
111 |
-
' </span>'+
|
112 |
-
'</div>'+
|
113 |
-
'<div class="ws_editbox" style="display: none;">'+buildEditboxFields(menu)+'</div>'+
|
114 |
-
'</div>';
|
115 |
-
|
116 |
-
var menu_obj = $(s).appendTo('#ws_menu_box');
|
117 |
-
|
118 |
-
//Apply flags based on the item's state
|
119 |
-
if (menu.missing && !menu.custom) {
|
120 |
-
addMenuFlag(menu_obj, 'missing');
|
121 |
-
}
|
122 |
-
if (menu.hidden) {
|
123 |
-
addMenuFlag(menu_obj, 'hidden');
|
124 |
-
}
|
125 |
-
if (menu.unused) {
|
126 |
-
addMenuFlag(menu_obj, 'unused');
|
127 |
-
}
|
128 |
-
if (menu.custom) {
|
129 |
-
addMenuFlag(menu_obj, 'custom_item');
|
130 |
-
}
|
131 |
-
|
132 |
-
//Create a container for menu items, even if there are none
|
133 |
-
$('#ws_submenu_box').append('<div class="ws_submenu" id="'+submenu_id+'" style="display:none;"></div>');
|
134 |
-
|
135 |
-
//Only show menus that have items.
|
136 |
-
//Skip arrays (with a length) because filled menus are encoded as custom objects ().
|
137 |
-
if (menu.items && (typeof menu.items != 'Array')){
|
138 |
-
var i = 0;
|
139 |
-
for (var item_file in menu.items){
|
140 |
-
outputMenuEntry(menu.items[item_file], i, submenu_id);
|
141 |
-
i++;
|
142 |
-
}
|
143 |
-
}
|
144 |
-
}
|
145 |
-
|
146 |
-
function outputMenuEntry(entry, ind, parent){
|
147 |
-
if (!entry.defaults) return;
|
148 |
-
|
149 |
-
var item = $(
|
150 |
-
'<div class="ws_container ws_item">'+
|
151 |
-
'<div class="ws_item_head">'+
|
152 |
-
'<a class="ws_edit_link"> </a>'+
|
153 |
-
'<div class="ws_flag_container"> </div>'+
|
154 |
-
'<span class="ws_item_title">'+
|
155 |
-
((entry.menu_title!=null)?entry.menu_title:entry.defaults.menu_title)+
|
156 |
-
' </span>'+
|
157 |
-
'</div>'+
|
158 |
-
'<div class="ws_editbox" style="display:none;">'+buildEditboxFields(entry)+'</div>'+
|
159 |
-
'<div>'
|
160 |
-
).appendTo('#'+parent)
|
161 |
-
|
162 |
-
//Apply flags based on the item's state
|
163 |
-
if (entry.missing && !entry.custom) {
|
164 |
-
addMenuFlag(item, 'missing');
|
165 |
-
}
|
166 |
-
if (entry.hidden) {
|
167 |
-
addMenuFlag(item, 'hidden');
|
168 |
-
}
|
169 |
-
if (entry.unused) {
|
170 |
-
addMenuFlag(item, 'unused');
|
171 |
-
}
|
172 |
-
if (entry.custom) {
|
173 |
-
addMenuFlag(item, 'custom_item');
|
174 |
-
}
|
175 |
-
}
|
176 |
-
|
177 |
-
function buildEditboxField(entry, field_name, field_caption){
|
178 |
-
if (entry[field_name]===undefined) {
|
179 |
-
return ''; //skip fields this entry doesn't have
|
180 |
-
}
|
181 |
-
|
182 |
-
return '<div class="ws_edit_field" field_name="'+field_name+'">' + (field_caption) + '<br />' +
|
183 |
-
'<input type="text" value="'+escapeJS((entry[field_name]!=null)?entry[field_name]:entry.defaults[field_name])+
|
184 |
-
'" default=\''+escapeJS(entry.defaults[field_name])+'\''+
|
185 |
-
' class="'+((entry[field_name]==null)?'ws_input_default':'')+'">'+
|
186 |
-
'<span class="ws_reset_button">[default]</span></div>';
|
187 |
-
}
|
188 |
-
|
189 |
-
function buildEditboxFields(entry){
|
190 |
-
var fields = {
|
191 |
-
'menu_title' : "Menu title",
|
192 |
-
'page_title' : "Page title",
|
193 |
-
'access_level' : 'Access level',
|
194 |
-
'file' : 'File',
|
195 |
-
'css_class' : 'CSS class',
|
196 |
-
'hookname' : 'CSS ID',
|
197 |
-
'icon_url' : 'Icon URL'
|
198 |
-
};
|
199 |
-
var s = '';
|
200 |
-
|
201 |
-
for (var field_name in fields){
|
202 |
-
s = s + buildEditboxField(entry, field_name, fields[field_name]);
|
203 |
-
}
|
204 |
-
|
205 |
-
//Add the "Custom item" checkbox
|
206 |
-
var is_custom = false;
|
207 |
-
if ( typeof(entry['custom']) != 'undefined' ){
|
208 |
-
is_custom = entry['custom'];
|
209 |
-
}
|
210 |
-
s = s +
|
211 |
-
'<div class="ws_edit_field">'+
|
212 |
-
'<label title="Custom items are visible even if they\'re not present in the default WordPress menu">'+
|
213 |
-
'<input type="checkbox" class="ws_custom_toggle"'+
|
214 |
-
(is_custom?' checked="checked"':'')+ '> Custom</label>'+
|
215 |
-
'</div>';
|
216 |
-
|
217 |
-
return s;
|
218 |
-
}
|
219 |
-
|
220 |
-
//Encode the current menu structure as JSON
|
221 |
-
function encodeMenuAsJSON(){
|
222 |
-
var data = {};
|
223 |
-
var separator_count = 0;
|
224 |
-
var menu_position = 0;
|
225 |
-
|
226 |
-
//Iterate over all menus
|
227 |
-
$('#ws_menu_box .ws_menu').each(function(i) {
|
228 |
-
|
229 |
-
var menu_obj = {};
|
230 |
-
menu_obj.defaults = {};
|
231 |
-
|
232 |
-
menu_position++;
|
233 |
-
menu_obj.position = menu_position;
|
234 |
-
menu_obj.defaults.position = menu_position; //the real default value will later overwrite this
|
235 |
-
|
236 |
-
var filename = $(this).find('.ws_edit_field[field_name="file"] input').val();
|
237 |
-
//Check if this is a separator
|
238 |
-
if ( $(this).hasClass('ws_menu_separator') ){
|
239 |
-
menu_obj.separator = true;
|
240 |
-
if ( filename=='' ) {
|
241 |
-
filename = 'separator_'+separator_count+'_';
|
242 |
-
}
|
243 |
-
separator_count++;
|
244 |
-
}
|
245 |
-
|
246 |
-
//Iterate over all fields of the menu
|
247 |
-
$(this).find('.ws_edit_field').each(function() {
|
248 |
-
//Get the name of this field
|
249 |
-
field_name = $(this).attr('field_name');
|
250 |
-
//Skip if unnamed
|
251 |
-
if (!field_name) return true;
|
252 |
-
|
253 |
-
input_box = $(this).find('input');
|
254 |
-
//Save null if default used, custom value otherwise
|
255 |
-
if (input_box.hasClass('ws_input_default')){
|
256 |
-
menu_obj[field_name] = null;
|
257 |
-
} else {
|
258 |
-
menu_obj[field_name] = input_box.val();
|
259 |
-
}
|
260 |
-
menu_obj.defaults[field_name]=input_box.attr('default');
|
261 |
-
|
262 |
-
});
|
263 |
-
//Check if the menu is hidden
|
264 |
-
menu_obj.hidden = $(this).hasClass('ws_hidden');
|
265 |
-
//Check if this is a custom menu
|
266 |
-
menu_obj.custom = $(this).hasClass('ws_custom_item');
|
267 |
-
|
268 |
-
menu_obj.items = {};
|
269 |
-
|
270 |
-
var item_position = 0;
|
271 |
-
|
272 |
-
//Iterate over the menu's items, if any
|
273 |
-
$('#'+$(this).attr('submenu_id')).find('.ws_item').each(function (i) {
|
274 |
-
var filename = $(this).find('.ws_edit_field[field_name="file"] input').val();
|
275 |
-
|
276 |
-
var item = {};
|
277 |
-
item.defaults = {};
|
278 |
-
|
279 |
-
//Save the position data (probably not all that useful)
|
280 |
-
item_position++;
|
281 |
-
item.position = item_position;
|
282 |
-
item.defaults.position = item_position;
|
283 |
-
|
284 |
-
//Iterate over all fields of the item
|
285 |
-
$(this).find('.ws_edit_field').each(function() {
|
286 |
-
//Get the name of this field
|
287 |
-
field_name = $(this).attr('field_name');
|
288 |
-
//Skip if unnamed
|
289 |
-
if (!field_name) return true;
|
290 |
-
|
291 |
-
input_box = $(this).find('input');
|
292 |
-
//Save null if default used, custom value otherwise
|
293 |
-
if (input_box.hasClass('ws_input_default')){
|
294 |
-
item[field_name] = null;
|
295 |
-
} else {
|
296 |
-
item[field_name] = input_box.val();
|
297 |
-
}
|
298 |
-
item.defaults[field_name]=input_box.attr('default');
|
299 |
-
|
300 |
-
});
|
301 |
-
//Check if the item is hidden
|
302 |
-
if ($(this).hasClass('ws_hidden')){
|
303 |
-
item.hidden = true;
|
304 |
-
}
|
305 |
-
//Check if this is a custom item
|
306 |
-
item.custom = $(this).hasClass('ws_custom_item');
|
307 |
-
|
308 |
-
//Save the item in the parent menu
|
309 |
-
menu_obj.items[filename] = item;
|
310 |
-
});
|
311 |
-
//*/
|
312 |
-
|
313 |
-
//Attach the menu to the main struct
|
314 |
-
data[filename] = menu_obj;
|
315 |
-
|
316 |
-
});
|
317 |
-
|
318 |
-
return $.toJSON(data);
|
319 |
-
}
|
320 |
-
|
321 |
-
var item_flags = {
|
322 |
-
'custom_item' : 'This is a custom menu item',
|
323 |
-
'unused' : 'This item was automatically (re)inserted into your custom menu because it is present in the default WordPress menu',
|
324 |
-
'missing' : 'This item is not present in the default WordPress menu. Tick the "Custom" checkbox if you want it to be visible anyway.',
|
325 |
-
'hidden' : 'This item is hidden'
|
326 |
-
}
|
327 |
-
|
328 |
-
//These function manipulate the menu flags (e.g. hidden, custom, etc)
|
329 |
-
function addMenuFlag(item, flag){
|
330 |
-
item = $(item);
|
331 |
-
|
332 |
-
var item_class = 'ws_' + flag;
|
333 |
-
var img_class = 'ws_' + flag + '_flag';
|
334 |
-
|
335 |
-
item.addClass(item_class);
|
336 |
-
//Add the flag image
|
337 |
-
var flag_container = item.find('.ws_flag_container');
|
338 |
-
if ( flag_container.find('.' + img_class).length == 0 ){
|
339 |
-
flag_container.append('<div class="ws_flag '+img_class+'" title="'+item_flags[flag]+'"></div>');
|
340 |
-
}
|
341 |
-
}
|
342 |
-
|
343 |
-
function removeMenuFlag(item, flag){
|
344 |
-
item = $(item);
|
345 |
-
var item_class = 'ws_' + flag;
|
346 |
-
var img_class = 'ws_' + flag + '_flag';
|
347 |
-
|
348 |
-
item.removeClass('ws_' + flag);
|
349 |
-
item.find('.' + img_class).remove();
|
350 |
-
}
|
351 |
-
|
352 |
-
function toggleMenuFlag(item, flag){
|
353 |
-
if (menuHasFlag(item, flag)){
|
354 |
-
removeMenuFlag(item, flag);
|
355 |
-
} else {
|
356 |
-
addMenuFlag(item, flag);
|
357 |
-
}
|
358 |
-
}
|
359 |
-
|
360 |
-
function menuHasFlag(item, flag){
|
361 |
-
return $(item).hasClass('ws_'+flag);
|
362 |
-
}
|
363 |
-
|
364 |
-
function clearMenuFlags(item){
|
365 |
-
item = $(item);
|
366 |
-
item.find('.ws_flag').remove();
|
367 |
-
for(var flag in item_flags){
|
368 |
-
item.removeClass('ws_'+flag);
|
369 |
-
}
|
370 |
-
}
|
371 |
-
|
372 |
-
//Cut & paste stuff
|
373 |
-
var menu_in_clipboard = null;
|
374 |
-
var submenu_in_clipboard = null;
|
375 |
-
var item_in_clipboard = null;
|
376 |
-
var ws_paste_count = 0;
|
377 |
-
|
378 |
-
$(document).ready(function(){
|
379 |
-
|
380 |
-
//Show the default menu
|
381 |
-
outputWpMenu(customMenu);
|
382 |
-
|
383 |
-
//Make the top menu box sortable (we only need to do this once)
|
384 |
-
$('#ws_menu_box').sortable({
|
385 |
-
items: '> .ws_container',
|
386 |
-
cursor: 'move',
|
387 |
-
dropOnEmpty: true,
|
388 |
-
});
|
389 |
-
|
390 |
-
//===== Toolbar buttons =======
|
391 |
-
//Show/Hide menu
|
392 |
-
$('#ws_hide_menu').click(function () {
|
393 |
-
//Get the selected menu
|
394 |
-
var selection = $('#ws_menu_box .ws_active');
|
395 |
-
if (!selection.length) return;
|
396 |
-
|
397 |
-
//Mark the menu as hidden/visible
|
398 |
-
//selection.toggleClass('ws_hidden');
|
399 |
-
toggleMenuFlag(selection, 'hidden');
|
400 |
-
|
401 |
-
//Also mark all of it's submenus as hidden/visible
|
402 |
-
if ( menuHasFlag(selection,'hidden') ){
|
403 |
-
$('#' + selection.attr('submenu_id') + ' .ws_item').each(function(){
|
404 |
-
addMenuFlag(this, 'hidden');
|
405 |
-
});
|
406 |
-
} else {
|
407 |
-
$('#' + selection.attr('submenu_id') + ' .ws_item').each(function(){
|
408 |
-
removeMenuFlag(this, 'hidden');
|
409 |
-
});
|
410 |
-
}
|
411 |
-
});
|
412 |
-
|
413 |
-
//Delete menu
|
414 |
-
$('#ws_delete_menu').click(function () {
|
415 |
-
//Get the selected menu
|
416 |
-
var selection = $('#ws_menu_box .ws_active');
|
417 |
-
if (!selection.length) return;
|
418 |
-
|
419 |
-
if (confirm('Are you sure you want to delete this menu?')){
|
420 |
-
//Delete the submenu first
|
421 |
-
$('#' + selection.attr('submenu_id')).remove();
|
422 |
-
//Delete the menu
|
423 |
-
selection.remove();
|
424 |
-
}
|
425 |
-
});
|
426 |
-
|
427 |
-
//Copy menu
|
428 |
-
$('#ws_copy_menu').click(function () {
|
429 |
-
//Get the selected menu
|
430 |
-
var selection = $('#ws_menu_box .ws_active');
|
431 |
-
if (!selection.length) return;
|
432 |
-
|
433 |
-
//Store a copy in clipboard
|
434 |
-
menu_in_clipboard = selection.clone(true); //just like that
|
435 |
-
menu_in_clipboard.removeClass('ws_active');
|
436 |
-
submenu_in_clipboard = $('#'+selection.attr('submenu_id')).clone(true);
|
437 |
-
});
|
438 |
-
|
439 |
-
//Cut menu
|
440 |
-
$('#ws_cut_menu').click(function () {
|
441 |
-
//Get the selected menu
|
442 |
-
var selection = $('#ws_menu_box .ws_active');
|
443 |
-
if (!selection.length) return;
|
444 |
-
|
445 |
-
//Store a copy of both menu and it's submenu in clipboard
|
446 |
-
menu_in_clipboard = selection.removeClass('ws_active').clone(true);
|
447 |
-
menu_in_clipboard.removeClass('ws_active');
|
448 |
-
submenu_in_clipboard = $('#'+selection.attr('submenu_id')).clone(true);
|
449 |
-
//Remove the original menu and submenu
|
450 |
-
selection.remove();
|
451 |
-
$('#'+selection.attr('submenu_id')).remove;
|
452 |
-
});
|
453 |
-
|
454 |
-
//Paste menu
|
455 |
-
$('#ws_paste_menu').click(function () {
|
456 |
-
//Check if anything has been copied/cut
|
457 |
-
if (!menu_in_clipboard) return;
|
458 |
-
//Get the selected menu
|
459 |
-
var selection = $('#ws_menu_box .ws_active');
|
460 |
-
|
461 |
-
ws_paste_count++;
|
462 |
-
|
463 |
-
//Clone new objects from the virtual clipboard
|
464 |
-
var new_menu = menu_in_clipboard.clone(true);
|
465 |
-
var new_submenu = submenu_in_clipboard.clone(true);
|
466 |
-
//Close submenu editboxes
|
467 |
-
new_submenu.find('.ws_editbox').hide();
|
468 |
-
|
469 |
-
//The cloned menu must have a unique file name, unless it's a separator
|
470 |
-
if (!new_menu.hasClass('ws_menu_separator')) {
|
471 |
-
new_menu.find('.ws_edit_field[field_name="file"] input').val('custom_menu_'+ws_paste_count);
|
472 |
-
}
|
473 |
-
|
474 |
-
//The cloned submenu needs a unique ID (could be improved)
|
475 |
-
new_submenu.attr('id', 'ws-pasted-obj-'+ws_paste_count);
|
476 |
-
new_menu.attr('submenu_id', 'ws-pasted-obj-'+ws_paste_count);
|
477 |
-
|
478 |
-
//Make the new submenu sortable
|
479 |
-
new_submenu.sortable({
|
480 |
-
items: '> .ws_container',
|
481 |
-
cursor: 'move',
|
482 |
-
dropOnEmpty: true,
|
483 |
-
});
|
484 |
-
|
485 |
-
if (selection.length > 0) {
|
486 |
-
//If a menu is selected add the pasted item after it
|
487 |
-
selection.after(new_menu);
|
488 |
-
} else {
|
489 |
-
//Otherwise add the pasted item at the end
|
490 |
-
$('#ws_menu_box').append(new_menu);
|
491 |
-
};
|
492 |
-
|
493 |
-
//Insert the submenu in the box, too
|
494 |
-
$('#ws_submenu_box').append(new_submenu);
|
495 |
-
|
496 |
-
new_menu.show();
|
497 |
-
new_submenu.hide();
|
498 |
-
});
|
499 |
-
|
500 |
-
//New menu
|
501 |
-
$('#ws_new_menu').click(function () {
|
502 |
-
ws_paste_count++;
|
503 |
-
|
504 |
-
//This is a hack.
|
505 |
-
//Clone another menu to use as a template
|
506 |
-
var menu = $('#ws_menu_box .ws_menu:first').clone(true);
|
507 |
-
//Also clone a submenu
|
508 |
-
var submenu = $('#' + menu.attr('submenu_id')).clone(true);
|
509 |
-
//Assign a new ID
|
510 |
-
submenu.attr('id', 'ws-new-submenu-'+ws_paste_count);
|
511 |
-
menu.attr('submenu_id', 'ws-new-submenu-'+ws_paste_count);
|
512 |
-
//Remove all items from the submenu
|
513 |
-
submenu.empty();
|
514 |
-
//Make the submenu sortable
|
515 |
-
submenu.sortable({
|
516 |
-
items: '> .ws_container',
|
517 |
-
cursor: 'move',
|
518 |
-
dropOnEmpty: true,
|
519 |
-
});
|
520 |
-
|
521 |
-
//Clean up the menu's flags & classes
|
522 |
-
menu.attr('class','ws_container ws_menu');
|
523 |
-
clearMenuFlags(menu);
|
524 |
-
addMenuFlag(menu, 'custom_item');
|
525 |
-
|
526 |
-
//Check the "Custom" checkbox
|
527 |
-
menu.find('.ws_custom_toggle').attr('checked', 'checked');
|
528 |
-
|
529 |
-
var temp_id = 'custom_menu_'+ws_paste_count;
|
530 |
-
//Assign a stub title
|
531 |
-
menu.find('.ws_item_title').text('Custom Menu '+ws_paste_count);
|
532 |
-
//All fields start out set to defaults
|
533 |
-
menu.find('input').attr('default','').addClass('ws_input_default');
|
534 |
-
//Set all fields
|
535 |
-
menu.find('.ws_edit_field[field_name="page_title"] input').val('').attr('default','');
|
536 |
-
menu.find('.ws_edit_field[field_name="menu_title"] input').val('Custom Menu '+ws_paste_count).attr('default','Custom Menu '+ws_paste_count);
|
537 |
-
menu.find('.ws_edit_field[field_name="access_level"] input').val('read').attr('default','read');
|
538 |
-
menu.find('.ws_edit_field[field_name="file"] input').val(temp_id).attr('default',temp_id);
|
539 |
-
menu.find('.ws_edit_field[field_name="css_class"] input').val('menu-top').attr('default','menu-top');
|
540 |
-
menu.find('.ws_edit_field[field_name="icon_url"] input').val('images/generic.png').attr('default','images/generic.png');
|
541 |
-
menu.find('.ws_edit_field[field_name="hookname"] input').val(temp_id).attr('default',temp_id);
|
542 |
-
|
543 |
-
//The menus's editbox is always open
|
544 |
-
menu.find('.ws_editbox').show();
|
545 |
-
//Make sure the edit link is in the right state, too
|
546 |
-
menu.find('.ws_edit_link').addClass('ws_edit_link_expanded');
|
547 |
-
|
548 |
-
//Finally, insert the menu into the box
|
549 |
-
$('#ws_menu_box').append(menu);
|
550 |
-
//And insert the submenu
|
551 |
-
$('#ws_submenu_box').append(submenu);
|
552 |
-
});
|
553 |
-
|
554 |
-
//===== Item toolbar buttons =======
|
555 |
-
//Show/Hide item
|
556 |
-
$('#ws_hide_item').click(function () {
|
557 |
-
//Get the selected item
|
558 |
-
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
559 |
-
if (!selection.length) return;
|
560 |
-
|
561 |
-
//Mark the item as hidden
|
562 |
-
toggleMenuFlag(selection, 'hidden');
|
563 |
-
});
|
564 |
-
|
565 |
-
//Delete menu
|
566 |
-
$('#ws_delete_item').click(function () {
|
567 |
-
//Get the selected menu
|
568 |
-
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
569 |
-
if (!selection.length) return;
|
570 |
-
|
571 |
-
if (confirm('Are you sure you want to delete this menu item?')){
|
572 |
-
//Delete the item
|
573 |
-
selection.remove();
|
574 |
-
}
|
575 |
-
});
|
576 |
-
|
577 |
-
//Copy item
|
578 |
-
$('#ws_copy_item').click(function () {
|
579 |
-
//Get the selected item
|
580 |
-
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
581 |
-
if (!selection.length) return;
|
582 |
-
|
583 |
-
//Store a copy in clipboard
|
584 |
-
item_in_clipboard = selection.clone(true); //just like that
|
585 |
-
item_in_clipboard.removeClass('ws_active');
|
586 |
-
});
|
587 |
-
|
588 |
-
//Cut item
|
589 |
-
$('#ws_cut_item').click(function () {
|
590 |
-
//Get the selected item
|
591 |
-
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
592 |
-
if (!selection.length) return;
|
593 |
-
|
594 |
-
//Store a the item in clipboard
|
595 |
-
item_in_clipboard = selection.clone(true);
|
596 |
-
item_in_clipboard.removeClass('ws_active');
|
597 |
-
//Remove the original item
|
598 |
-
selection.remove();
|
599 |
-
});
|
600 |
-
|
601 |
-
//Paste item
|
602 |
-
$('#ws_paste_item').click(function () {
|
603 |
-
//Check if anything has been copied/cut
|
604 |
-
if (!item_in_clipboard) return;
|
605 |
-
//Get the selected menu
|
606 |
-
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
607 |
-
|
608 |
-
ws_paste_count++;
|
609 |
-
|
610 |
-
//Clone a new object from the virtual clipboard
|
611 |
-
var new_item = item_in_clipboard.clone(true);
|
612 |
-
//The item's editbox is always closed
|
613 |
-
new_item.find('.ws_editbox').hide();
|
614 |
-
|
615 |
-
if (selection.length > 0) {
|
616 |
-
//If an item is selected add the pasted item after it
|
617 |
-
selection.after(new_item);
|
618 |
-
} else {
|
619 |
-
//Otherwise add the pasted item at the end
|
620 |
-
$('#ws_submenu_box .ws_submenu:visible').append(new_item);
|
621 |
-
};
|
622 |
-
|
623 |
-
new_item.show();
|
624 |
-
});
|
625 |
-
|
626 |
-
//New item
|
627 |
-
$('#ws_new_item').click(function () {
|
628 |
-
if ($('.ws_submenu:visible').length<1) return; //abort if no submenu visible
|
629 |
-
|
630 |
-
ws_paste_count++;
|
631 |
-
|
632 |
-
//Clone another item to use as a template (hack)
|
633 |
-
var menu = $('#ws_submenu_box .ws_item:first').clone(true);
|
634 |
-
|
635 |
-
//Cleanup the items's flags & classes
|
636 |
-
menu.attr('class','ws_container ws_item');
|
637 |
-
clearMenuFlags(menu);
|
638 |
-
addMenuFlag(menu, 'custom_item');
|
639 |
-
|
640 |
-
//Check the "Custom" checkbox
|
641 |
-
menu.find('.ws_custom_toggle').attr('checked', 'checked');
|
642 |
-
|
643 |
-
var temp_id = 'custom_item_'+ws_paste_count;
|
644 |
-
//Assign a stub title
|
645 |
-
menu.find('.ws_item_title').text('Custom Item '+ws_paste_count);
|
646 |
-
//All fields start out set to defaults
|
647 |
-
menu.find('input').attr('default','').addClass('ws_input_default');
|
648 |
-
//Set all fields
|
649 |
-
menu.find('.ws_edit_field[field_name="page_title"] input').val('').attr('default','');
|
650 |
-
menu.find('.ws_edit_field[field_name="menu_title"] input').val('Custom Item '+ws_paste_count).attr('default','Custom Item '+ws_paste_count);
|
651 |
-
menu.find('.ws_edit_field[field_name="access_level"] input').val('read').attr('default','read');
|
652 |
-
menu.find('.ws_edit_field[field_name="file"] input').val(temp_id).attr('default',temp_id);
|
653 |
-
|
654 |
-
//The items's editbox is always open
|
655 |
-
menu.find('.ws_editbox').show();
|
656 |
-
//Make sure the edit link is in the right state, too
|
657 |
-
menu.find('.ws_edit_link').addClass('ws_edit_link_expanded');
|
658 |
-
|
659 |
-
//Finally, insert the item into the box
|
660 |
-
$('.ws_submenu:visible').append(menu);
|
661 |
-
});
|
662 |
-
|
663 |
-
//==============================================
|
664 |
-
// Main buttons
|
665 |
-
//==============================================
|
666 |
-
|
667 |
-
//Save Changes - encode the current menu as JSON and save
|
668 |
-
$('#ws_save_menu').click(function () {
|
669 |
-
var data = encodeMenuAsJSON();
|
670 |
-
$('#ws_data').val(data);
|
671 |
-
$('#ws_main_form').submit();
|
672 |
-
});
|
673 |
-
|
674 |
-
//Load default menu - load the default WordPress menu
|
675 |
-
$('#ws_load_menu').click(function () {
|
676 |
-
if (confirm('Are you sure you want to load the default WordPress menu into the editor?')){
|
677 |
-
outputWpMenu(defaultMenu);
|
678 |
-
}
|
679 |
-
});
|
680 |
-
|
681 |
-
//Reset menu - re-load the custom menu = discards any changes made by user
|
682 |
-
$('#ws_reset_menu').click(function () {
|
683 |
-
if (confirm('Are you sure you want to reset the custom menu? Any unsaved changes will be lost!')){
|
684 |
-
outputWpMenu(customMenu);
|
685 |
-
}
|
686 |
-
});
|
687 |
-
|
688 |
-
});
|
689 |
-
|
690 |
-
})(jQuery);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
menu-editor.php
CHANGED
@@ -3,22 +3,17 @@
|
|
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: 0
|
7 |
Author: Janis Elsts
|
8 |
Author URI: http://w-shadow.com/blog/
|
9 |
*/
|
10 |
|
11 |
-
/*
|
12 |
-
Created by Janis Elsts (email : whiteshadow@w-shadow.com)
|
13 |
-
It's LGPL.
|
14 |
-
*/
|
15 |
-
|
16 |
//Are we running in the Dashboard?
|
17 |
if ( is_admin() ) {
|
18 |
|
19 |
//Load the plugin
|
20 |
-
require 'menu-editor-core.php';
|
21 |
-
$wp_menu_editor = new WPMenuEditor(__FILE__);
|
22 |
|
23 |
}//is_admin()
|
24 |
?>
|
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.0
|
7 |
Author: Janis Elsts
|
8 |
Author URI: http://w-shadow.com/blog/
|
9 |
*/
|
10 |
|
|
|
|
|
|
|
|
|
|
|
11 |
//Are we running in the Dashboard?
|
12 |
if ( is_admin() ) {
|
13 |
|
14 |
//Load the plugin
|
15 |
+
require 'includes/menu-editor-core.php';
|
16 |
+
$wp_menu_editor = new WPMenuEditor(__FILE__, 'ws_menu_editor');
|
17 |
|
18 |
}//is_admin()
|
19 |
?>
|
readme.txt
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
=== Admin Menu Editor ===
|
2 |
Contributors: whiteshadow
|
3 |
-
Donate link:
|
4 |
Tags: admin, dashboard, menu, security, wpmu
|
5 |
-
Requires at least:
|
6 |
-
Tested up to:
|
7 |
-
Stable tag: 0
|
8 |
|
9 |
Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
10 |
|
@@ -13,44 +13,72 @@ Admin Menu Editor lets you manually edit the Dashboard menu. You can reorder the
|
|
13 |
|
14 |
**Features**
|
15 |
|
|
|
16 |
* Sort menu items via drag & drop.
|
17 |
* Move a menu item to a different submenu via cut & paste.
|
18 |
-
* Edit any existing menu - change the title, access rights, menu icon and so on. Note that you can't lower the required access rights, but you can change them to be more restrictive.
|
19 |
* Hide/show any menu or menu item. A hidden menu is invisible to all users, including administrators.
|
20 |
-
* Create custom menus that point to any part of the Dashboard
|
|
|
|
|
21 |
|
22 |
[Suggest new features and improvements here](http://feedback.w-shadow.com/forums/58572-admin-menu-editor)
|
23 |
|
24 |
**Known Issues**
|
25 |
|
26 |
* If you delete any of the default menus they will reappear after saving. This is by design. To get rid of a menu for good, either hide it or set it's access rights to a higher level.
|
27 |
-
*
|
28 |
-
*
|
|
|
29 |
|
30 |
== Installation ==
|
31 |
|
32 |
**Normal installation**
|
33 |
|
34 |
-
1. Download the admin-menu-editor.zip file to your
|
35 |
1. Unzip the file.
|
36 |
1. Upload the `admin-menu-editor` directory to your `/wp-content/plugins/` directory.
|
37 |
1. Activate the plugin through the 'Plugins' menu in WordPress.
|
38 |
|
39 |
That's it. You can access the the menu editor by going to *Settings -> Menu Editor*. The plugin will automatically load your current menu configuration the first time you run it.
|
40 |
|
41 |
-
**
|
42 |
|
43 |
-
If you have
|
44 |
|
45 |
-
1. Download the admin-menu-editor.zip file to your
|
46 |
1. Unzip the file.
|
47 |
-
1.
|
48 |
-
1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
-
*Note : It is currently not possible to install this plugin both as a normal plugin and as a mu-plugin on the same site.*
|
51 |
|
52 |
== Changelog ==
|
53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
= 0.2 =
|
55 |
* Provisional WPMU support.
|
56 |
* Missing and unused menu items now get different icons in the menu editor.
|
1 |
=== Admin Menu Editor ===
|
2 |
Contributors: whiteshadow
|
3 |
+
Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
|
4 |
Tags: admin, dashboard, menu, security, wpmu
|
5 |
+
Requires at least: 3.0
|
6 |
+
Tested up to: 3.0
|
7 |
+
Stable tag: 1.0
|
8 |
|
9 |
Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
10 |
|
13 |
|
14 |
**Features**
|
15 |
|
16 |
+
* Edit any existing menu - change the title, access rights, menu icon and so on.
|
17 |
* Sort menu items via drag & drop.
|
18 |
* Move a menu item to a different submenu via cut & paste.
|
|
|
19 |
* Hide/show any menu or menu item. A hidden menu is invisible to all users, including administrators.
|
20 |
+
* Create custom menus that point to any part of the Dashboard or an external URL.
|
21 |
+
|
22 |
+
The [Pro version](http://wpplugins.com/plugin/146/admin-menu-editor-pro) of the plugin lets you also import/export menu configurations, make menu items open in a new window, and use [shortcodes](http://wpplugins.com/plugin/146/admin-menu-editor-pro?view=notes) in the Dashboard menu.
|
23 |
|
24 |
[Suggest new features and improvements here](http://feedback.w-shadow.com/forums/58572-admin-menu-editor)
|
25 |
|
26 |
**Known Issues**
|
27 |
|
28 |
* If you delete any of the default menus they will reappear after saving. This is by design. To get rid of a menu for good, either hide it or set it's access rights to a higher level.
|
29 |
+
* If one of your menu items is only visible in the editor but not the Dashboard menu itself, make sure its "Custom" checkbox is ticked. The plugin will usually do this for you when you create a new menu.
|
30 |
+
* You can't lower a menu's required access rights, but you can change them to be more restrictive.
|
31 |
+
|
32 |
|
33 |
== Installation ==
|
34 |
|
35 |
**Normal installation**
|
36 |
|
37 |
+
1. Download the admin-menu-editor.zip file to your computer.
|
38 |
1. Unzip the file.
|
39 |
1. Upload the `admin-menu-editor` directory to your `/wp-content/plugins/` directory.
|
40 |
1. Activate the plugin through the 'Plugins' menu in WordPress.
|
41 |
|
42 |
That's it. You can access the the menu editor by going to *Settings -> Menu Editor*. The plugin will automatically load your current menu configuration the first time you run it.
|
43 |
|
44 |
+
**WP MultiSite installation**
|
45 |
|
46 |
+
If you have WordPress set up in multisite ("Network") mode, you can also install Admin Menu Editor as a global plugin. This will enable you to edit the Dashboard menu for all sites and users at once.
|
47 |
|
48 |
+
1. Download the admin-menu-editor.zip file to your computer.
|
49 |
1. Unzip the file.
|
50 |
+
1. Create a new directory named `mu-plugins` in your site's `wp-content` directory (unless it already exists).
|
51 |
+
1. Upload the `admin-menu-editor` directory to `/wp-content/mu-plugins/`.
|
52 |
+
1. Move `admin-menu-editor-mu.php` from `admin-menu-editor/includes` to `/wp-content/mu-plugins/`.
|
53 |
+
|
54 |
+
Plugins installed in the `mu-plugins` directory are treated as "always on", so you don't need to explicitly activate the menu editor. Just go to *Settings -> Menu Editor* and start customizing your admin menu :)
|
55 |
+
|
56 |
+
*Notes*
|
57 |
+
* Instead of installing Admin Menu Editor in `mu-plugins`, you can also install it normally and then activate it globally via "Network Activate". However, this will make the plugin visible to normal users when it is inactive (e.g. during upgrades).
|
58 |
+
* When Admin Menu Editor is installed in `mu-plugins` or activated via "Network Activate", only the "super admin" user can access the menu editor page. Other users will see the customized Dashboard menu, but be unable to edit it.
|
59 |
+
* It is currently not possible to install Admin Menu Editor as both a normal and global plugin on the same site.
|
60 |
|
|
|
61 |
|
62 |
== Changelog ==
|
63 |
|
64 |
+
= 1.0 =
|
65 |
+
* Added a "Feedback" link.
|
66 |
+
* Added a dropdown list of all roles and capabilities to the menu editor.
|
67 |
+
* Added toolbar buttons for sorting menu items alphabetically.
|
68 |
+
* New "Add separator" button.
|
69 |
+
* New separator graphics.
|
70 |
+
* Minimum requirements upped to WP 3.0.
|
71 |
+
* Compatibility with WP 3.0 MultiSite mode.
|
72 |
+
* Plugin pages moved to different menus no longer stop working.
|
73 |
+
* Fixed moved pages not having a window title.
|
74 |
+
* Hide advanced menu fields by default (can be turned off in Screen Options).
|
75 |
+
* Changed a lot of UI text to be a bit more intuitive.
|
76 |
+
* In emergencies, administrators can now reset the custom menu by going to http:://example.com/wp-admin/?reset\_admin\_menu=1
|
77 |
+
* Fixed the "Donate" link in readme.txt
|
78 |
+
* Unbundle the JSON.php JSON parser/encoder and use the built-in class-json.php instead.
|
79 |
+
* Use the native JSON decoding routine if it's available.
|
80 |
+
* Replaced the cryptic "Cannot redeclare whatever" activation error message with a more useful one.
|
81 |
+
|
82 |
= 0.2 =
|
83 |
* Provisional WPMU support.
|
84 |
* Missing and unused menu items now get different icons in the menu editor.
|
uninstall.php
CHANGED
@@ -11,7 +11,10 @@ if( defined( 'ABSPATH') && defined('WP_UNINSTALL_PLUGIN') ) {
|
|
11 |
|
12 |
//Remove the plugin's settings
|
13 |
delete_option('ws_menu_editor');
|
14 |
-
|
|
|
|
|
|
|
15 |
}
|
16 |
|
17 |
?>
|
11 |
|
12 |
//Remove the plugin's settings
|
13 |
delete_option('ws_menu_editor');
|
14 |
+
if ( function_exists('delete_site_option') ){
|
15 |
+
delete_site_option('ws_menu_editor');
|
16 |
+
}
|
17 |
+
|
18 |
}
|
19 |
|
20 |
?>
|