Version Description
- First release on wordpress.org
- Moved all images into a separate directory.
- Added a readme.txt
Download this release
Release Info
Developer | whiteshadow |
Plugin | Admin Menu Editor |
Version | 0.1.5 |
Comparing to | |
See all releases |
Version 0.1.5
- JSON.php +805 -0
- images/bullet_arrow_down2.png +0 -0
- images/bullet_error.png +0 -0
- images/cut.png +0 -0
- images/delete.png +0 -0
- images/page_white_add.png +0 -0
- images/page_white_copy.png +0 -0
- images/page_white_delete.png +0 -0
- images/page_white_paste.png +0 -0
- images/plugin_disabled.png +0 -0
- jquery.json-1.3.js +156 -0
- menu-editor-core.php +617 -0
- menu-editor.css +155 -0
- menu-editor.js +586 -0
- menu-editor.php +24 -0
- readme.txt +50 -0
- shadow_plugin_framework.php +176 -0
- uninstall.php +17 -0
JSON.php
ADDED
@@ -0,0 +1,805 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
?>
|
images/bullet_arrow_down2.png
ADDED
Binary file
|
images/bullet_error.png
ADDED
Binary file
|
images/cut.png
ADDED
Binary file
|
images/delete.png
ADDED
Binary file
|
images/page_white_add.png
ADDED
Binary file
|
images/page_white_copy.png
ADDED
Binary file
|
images/page_white_delete.png
ADDED
Binary file
|
images/page_white_paste.png
ADDED
Binary file
|
images/plugin_disabled.png
ADDED
Binary file
|
jquery.json-1.3.js
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* jQuery JSON Plugin
|
3 |
+
* version: 1.0 (2008-04-17)
|
4 |
+
*
|
5 |
+
* This document is licensed as free software under the terms of the
|
6 |
+
* MIT License: http://www.opensource.org/licenses/mit-license.php
|
7 |
+
*
|
8 |
+
* Brantley Harris technically wrote this plugin, but it is based somewhat
|
9 |
+
* on the JSON.org website's http://www.json.org/json2.js, which proclaims:
|
10 |
+
* "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
|
11 |
+
* I uphold. I really just cleaned it up.
|
12 |
+
*
|
13 |
+
* It is also based heavily on MochiKit's serializeJSON, which is
|
14 |
+
* copywrited 2005 by Bob Ippolito.
|
15 |
+
*/
|
16 |
+
|
17 |
+
(function($) {
|
18 |
+
function toIntegersAtLease(n)
|
19 |
+
// Format integers to have at least two digits.
|
20 |
+
{
|
21 |
+
return n < 10 ? '0' + n : n;
|
22 |
+
}
|
23 |
+
|
24 |
+
Date.prototype.toJSON = function(date)
|
25 |
+
// Yes, it polutes the Date namespace, but we'll allow it here, as
|
26 |
+
// it's damned usefull.
|
27 |
+
{
|
28 |
+
return this.getUTCFullYear() + '-' +
|
29 |
+
toIntegersAtLease(this.getUTCMonth()) + '-' +
|
30 |
+
toIntegersAtLease(this.getUTCDate());
|
31 |
+
};
|
32 |
+
|
33 |
+
var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g;
|
34 |
+
var meta = { // table of character substitutions
|
35 |
+
'\b': '\\b',
|
36 |
+
'\t': '\\t',
|
37 |
+
'\n': '\\n',
|
38 |
+
'\f': '\\f',
|
39 |
+
'\r': '\\r',
|
40 |
+
'"' : '\\"',
|
41 |
+
'\\': '\\\\'
|
42 |
+
};
|
43 |
+
|
44 |
+
$.quoteString = function(string)
|
45 |
+
// Places quotes around a string, inteligently.
|
46 |
+
// If the string contains no control characters, no quote characters, and no
|
47 |
+
// backslash characters, then we can safely slap some quotes around it.
|
48 |
+
// Otherwise we must also replace the offending characters with safe escape
|
49 |
+
// sequences.
|
50 |
+
{
|
51 |
+
if (escapeable.test(string))
|
52 |
+
{
|
53 |
+
return '"' + string.replace(escapeable, function (a)
|
54 |
+
{
|
55 |
+
var c = meta[a];
|
56 |
+
if (typeof c === 'string') {
|
57 |
+
return c;
|
58 |
+
}
|
59 |
+
c = a.charCodeAt();
|
60 |
+
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
|
61 |
+
}) + '"';
|
62 |
+
}
|
63 |
+
return '"' + string + '"';
|
64 |
+
};
|
65 |
+
|
66 |
+
$.toJSON = function(o, compact)
|
67 |
+
{
|
68 |
+
var type = typeof(o);
|
69 |
+
|
70 |
+
if (type == "undefined")
|
71 |
+
return "undefined";
|
72 |
+
else if (type == "number" || type == "boolean")
|
73 |
+
return o + "";
|
74 |
+
else if (o === null)
|
75 |
+
return "null";
|
76 |
+
|
77 |
+
// Is it a string?
|
78 |
+
if (type == "string")
|
79 |
+
{
|
80 |
+
return $.quoteString(o);
|
81 |
+
}
|
82 |
+
|
83 |
+
// Does it have a .toJSON function?
|
84 |
+
if (type == "object" && typeof o.toJSON == "function")
|
85 |
+
return o.toJSON(compact);
|
86 |
+
|
87 |
+
// Is it an array?
|
88 |
+
if (type != "function" && typeof(o.length) == "number")
|
89 |
+
{
|
90 |
+
var ret = [];
|
91 |
+
for (var i = 0; i < o.length; i++) {
|
92 |
+
ret.push( $.toJSON(o[i], compact) );
|
93 |
+
}
|
94 |
+
if (compact)
|
95 |
+
return "[" + ret.join(",") + "]";
|
96 |
+
else
|
97 |
+
return "[" + ret.join(", ") + "]";
|
98 |
+
}
|
99 |
+
|
100 |
+
// If it's a function, we have to warn somebody!
|
101 |
+
if (type == "function") {
|
102 |
+
throw new TypeError("Unable to convert object of type 'function' to json.");
|
103 |
+
}
|
104 |
+
|
105 |
+
// It's probably an object, then.
|
106 |
+
var ret = [];
|
107 |
+
for (var k in o) {
|
108 |
+
var name;
|
109 |
+
type = typeof(k);
|
110 |
+
|
111 |
+
if (type == "number")
|
112 |
+
name = '"' + k + '"';
|
113 |
+
else if (type == "string")
|
114 |
+
name = $.quoteString(k);
|
115 |
+
else
|
116 |
+
continue; //skip non-string or number keys
|
117 |
+
|
118 |
+
var val = $.toJSON(o[k], compact);
|
119 |
+
if (typeof(val) != "string") {
|
120 |
+
// skip non-serializable values
|
121 |
+
continue;
|
122 |
+
}
|
123 |
+
|
124 |
+
if (compact)
|
125 |
+
ret.push(name + ":" + val);
|
126 |
+
else
|
127 |
+
ret.push(name + ": " + val);
|
128 |
+
}
|
129 |
+
return "{" + ret.join(", ") + "}";
|
130 |
+
};
|
131 |
+
|
132 |
+
$.compactJSON = function(o)
|
133 |
+
{
|
134 |
+
return $.toJSON(o, true);
|
135 |
+
};
|
136 |
+
|
137 |
+
$.evalJSON = function(src)
|
138 |
+
// Evals JSON that we know to be safe.
|
139 |
+
{
|
140 |
+
return eval("(" + src + ")");
|
141 |
+
};
|
142 |
+
|
143 |
+
$.secureEvalJSON = function(src)
|
144 |
+
// Evals JSON in a way that is *more* secure.
|
145 |
+
{
|
146 |
+
var filtered = src;
|
147 |
+
filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@');
|
148 |
+
filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
|
149 |
+
filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
|
150 |
+
|
151 |
+
if (/^[\],:{}\s]*$/.test(filtered))
|
152 |
+
return eval("(" + src + ")");
|
153 |
+
else
|
154 |
+
throw new SyntaxError("Error parsing JSON, source is not valid.");
|
155 |
+
};
|
156 |
+
})(jQuery);
|
menu-editor-core.php
ADDED
@@ -0,0 +1,617 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
function __construct($plugin_file=''){
|
17 |
+
//Set some plugin-specific options
|
18 |
+
$this->option_name = 'ws_menu_editor';
|
19 |
+
$this->defaults = array(
|
20 |
+
);
|
21 |
+
|
22 |
+
$this->settings_link = 'options-general.php?page=menu_editor';
|
23 |
+
|
24 |
+
$this->magic_hooks = true;
|
25 |
+
$this->magic_hook_priority = 99999;
|
26 |
+
|
27 |
+
//Call the default constructor
|
28 |
+
if ( empty($plugin_file) ) $plugin_file = __FILE__;
|
29 |
+
parent::__construct($plugin_file);
|
30 |
+
|
31 |
+
//Build some template arrays
|
32 |
+
$this->blank_menu = array(
|
33 |
+
'page_title' => null,
|
34 |
+
'menu_title' => null,
|
35 |
+
'access_level' => null,
|
36 |
+
'file' => null,
|
37 |
+
'css_class' => null,
|
38 |
+
'hookname' => null,
|
39 |
+
'icon_url' => null,
|
40 |
+
'position' => null,
|
41 |
+
);
|
42 |
+
|
43 |
+
$this->blank_item = array(
|
44 |
+
'menu_title' => null,
|
45 |
+
'access_level' => null,
|
46 |
+
'file' => null,
|
47 |
+
'page_title' => null,
|
48 |
+
'position' => null,
|
49 |
+
);
|
50 |
+
|
51 |
+
}
|
52 |
+
|
53 |
+
//Backwards fompatible json_decode.
|
54 |
+
//We can't define this globally as that conflicts with the version created by Simple Tags.
|
55 |
+
function json_decode($data, $assoc=false){
|
56 |
+
$flag = $assoc?SERVICES_JSON_LOOSE_TYPE:0;
|
57 |
+
$json = new Services_JSON($flag);
|
58 |
+
return( $json->decode($data) );
|
59 |
+
}
|
60 |
+
|
61 |
+
//Backwards fompatible json_encode.
|
62 |
+
//Can't define this globally as that conflicts with Simple Tags.
|
63 |
+
function json_encode($data) {
|
64 |
+
$json = new Services_JSON();
|
65 |
+
return( $json->encode($data) );
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* WPMenuEditor::enqueue_scripts()
|
70 |
+
* Add the JS required by the editor to the page header
|
71 |
+
*
|
72 |
+
* @return void
|
73 |
+
*/
|
74 |
+
function enqueue_scripts(){
|
75 |
+
wp_enqueue_script('jquery');
|
76 |
+
wp_enqueue_script('jquery-ui-sortable');
|
77 |
+
|
78 |
+
//jQuery JSON plugin
|
79 |
+
wp_enqueue_script('jquery-json', $this->plugin_dir_url.'/jquery.json-1.3.js', array('jquery'), '1.3');
|
80 |
+
//Editor's scipts
|
81 |
+
wp_enqueue_script('menu-editor', $this->plugin_dir_url.'/menu-editor.js', array('jquery'));
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* WPMenuEditor::print_editor_css()
|
86 |
+
* Add the editor's CSS file to the page header
|
87 |
+
*
|
88 |
+
* @return void
|
89 |
+
*/
|
90 |
+
function print_editor_css(){
|
91 |
+
echo '<link type="text/css" rel="stylesheet" href="', $this->plugin_dir_url, '/menu-editor.css" />',"\n";
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* WPMenuEditor::hook_admin_menu()
|
96 |
+
* Create a configuration page and load the custom menu
|
97 |
+
*
|
98 |
+
* @return void
|
99 |
+
*/
|
100 |
+
function hook_admin_menu(){
|
101 |
+
global $menu, $submenu;
|
102 |
+
|
103 |
+
$page = add_options_page('Menu Editor', 'Menu Editor', 'manage_options', 'menu_editor', array(&$this, 'page_menu_editor'));
|
104 |
+
//Output our JS & CSS on that page only
|
105 |
+
add_action("admin_print_scripts-$page", array(&$this, 'enqueue_scripts'));
|
106 |
+
add_action("admin_print_scripts-$page", array(&$this, 'print_editor_css'));
|
107 |
+
|
108 |
+
$this->default_wp_menu = $menu;
|
109 |
+
$this->default_wp_submenu = $submenu;
|
110 |
+
|
111 |
+
//Is there a custom menu to use?
|
112 |
+
if ( !empty($this->options['custom_menu']) ){
|
113 |
+
//Merge in data from the default menu
|
114 |
+
$tree = $this->menu_merge($this->options['custom_menu'], $menu, $submenu);
|
115 |
+
//Apply the custom menu
|
116 |
+
list($menu, $submenu) = $this->tree2wp($tree);
|
117 |
+
//Save for later - the editor page will need it
|
118 |
+
$this->custom_menu = $tree;
|
119 |
+
//Re-filter the menu (silly WP should do that itself, oh well)
|
120 |
+
$this->filter_menu();
|
121 |
+
}
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* WPMenuEditor::filter_menu()
|
126 |
+
* Loop over the Dashboard submenus and remove pages for which the current user does not have privs.
|
127 |
+
*
|
128 |
+
* @return void
|
129 |
+
*/
|
130 |
+
function filter_menu(){
|
131 |
+
global $menu, $submenu, $_wp_submenu_nopriv, $_wp_menu_nopriv;
|
132 |
+
|
133 |
+
foreach ( array( 'submenu' ) as $sub_loop ) {
|
134 |
+
foreach ($$sub_loop as $parent => $sub) {
|
135 |
+
foreach ($sub as $index => $data) {
|
136 |
+
if ( ! current_user_can($data[1]) ) {
|
137 |
+
unset(${$sub_loop}[$parent][$index]);
|
138 |
+
$_wp_submenu_nopriv[$parent][$data[2]] = true;
|
139 |
+
}
|
140 |
+
}
|
141 |
+
|
142 |
+
if ( empty(${$sub_loop}[$parent]) )
|
143 |
+
unset(${$sub_loop}[$parent]);
|
144 |
+
}
|
145 |
+
}
|
146 |
+
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* WPMenuEditor::page_menu_editor()
|
151 |
+
* Output the menu editor page
|
152 |
+
*
|
153 |
+
* @return void
|
154 |
+
*/
|
155 |
+
function page_menu_editor(){
|
156 |
+
global $menu, $submenu;
|
157 |
+
if ( !current_user_can('manage_options') ){
|
158 |
+
die("Access denied");
|
159 |
+
}
|
160 |
+
|
161 |
+
?>
|
162 |
+
<div class="wrap">
|
163 |
+
<h2>Menu Editor</h2>
|
164 |
+
<?php
|
165 |
+
//Handle form submissions
|
166 |
+
if (isset($_POST['data'])){
|
167 |
+
check_admin_referer('menu-editor-form');
|
168 |
+
|
169 |
+
//Try to decode a menu tree encoded as JSON
|
170 |
+
$data = $this->json_decode($_POST['data'], true);
|
171 |
+
if (!$data){
|
172 |
+
echo "<!-- First decodind attempt failed, trying to fix with stripslashes() -->";
|
173 |
+
$fixed = stripslashes($_POST['data']);
|
174 |
+
$data = $this->json_decode( $fixed, true );
|
175 |
+
}
|
176 |
+
|
177 |
+
if ($data){
|
178 |
+
//Save the custom menu
|
179 |
+
$this->options['custom_menu'] = $data;
|
180 |
+
$this->save_options();
|
181 |
+
echo '<div id="message" class="updated fade"><p><strong>Settings saved. <a href="javascript:window.location.href=window.location.href">Reload the page</a> to see the modified menu.</strong></p></div>';
|
182 |
+
} else {
|
183 |
+
echo '<div id="message" class="error"><p><strong>Failed to decode input! The menu wasn\'t modified.</strong></p></div>';
|
184 |
+
}
|
185 |
+
}
|
186 |
+
|
187 |
+
//Build a tree struct. for the default menu
|
188 |
+
$default_menu = $this->wp2tree($this->default_wp_menu, $this->default_wp_submenu);
|
189 |
+
|
190 |
+
//Is there a custom menu?
|
191 |
+
if (!empty($this->options['custom_menu'])){
|
192 |
+
$custom_menu = $this->options['custom_menu'];
|
193 |
+
//Merge in the current defaults
|
194 |
+
$custom_menu = $this->menu_merge($custom_menu, $this->default_wp_menu, $this->default_wp_submenu);
|
195 |
+
} else {
|
196 |
+
//Start out with the default menu if there is no user-created one
|
197 |
+
$custom_menu = $default_menu;
|
198 |
+
}
|
199 |
+
|
200 |
+
//Encode both menus as JSON
|
201 |
+
$default_menu_js = $this->getMenuAsJS($default_menu);
|
202 |
+
$custom_menu_js = $this->getMenuAsJS($custom_menu);
|
203 |
+
|
204 |
+
$plugin_url = $this->plugin_dir_url;
|
205 |
+
$images_url = $this->plugin_dir_url . '/images';
|
206 |
+
?>
|
207 |
+
<div id='ws_menu_editor'>
|
208 |
+
<div id='ws_menu_box' class='ws_main_container'>
|
209 |
+
<div class='ws_toolbar'>
|
210 |
+
<a id='ws_cut_menu' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' /></a>
|
211 |
+
<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>
|
212 |
+
<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>
|
213 |
+
<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>
|
214 |
+
<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>
|
215 |
+
<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>
|
216 |
+
</div>
|
217 |
+
</div>
|
218 |
+
<div id='ws_submenu_box' class='ws_main_container'>
|
219 |
+
<div class='ws_toolbar'>
|
220 |
+
<a id='ws_cut_item' class='ws_button' href='javascript:void(0)' title='Cut'><img src='<?php echo $images_url; ?>/cut.png' /></a>
|
221 |
+
<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>
|
222 |
+
<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>
|
223 |
+
<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>
|
224 |
+
<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>
|
225 |
+
<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>
|
226 |
+
</div>
|
227 |
+
</div>
|
228 |
+
</div>
|
229 |
+
|
230 |
+
<div class="ws_main_container" style='width: 138px;'>
|
231 |
+
<form method="post" action="<?php echo admin_url('options-general.php?page=menu_editor'); ?>" id='ws_main_form' name='ws_main_form'>
|
232 |
+
<?php wp_nonce_field('menu-editor-form'); ?>
|
233 |
+
<input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes"
|
234 |
+
style="margin-bottom: 20px;" />
|
235 |
+
<input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
|
236 |
+
<input type="button" id='ws_reset_menu' value="Reset menu" class="button ws_main_button" />
|
237 |
+
<input type="hidden" name="data" id="ws_data" value="">
|
238 |
+
</form>
|
239 |
+
</div>
|
240 |
+
|
241 |
+
</div>
|
242 |
+
<script type='text/javascript'>
|
243 |
+
|
244 |
+
var defaultMenu = <?php echo $default_menu_js; ?>;
|
245 |
+
var customMenu = <?php echo $custom_menu_js; ?>;
|
246 |
+
|
247 |
+
</script>
|
248 |
+
<?php
|
249 |
+
}
|
250 |
+
|
251 |
+
/**
|
252 |
+
* WPMenuEditor::getMenuAsJS()
|
253 |
+
* Encode a menu tree as JSON
|
254 |
+
*
|
255 |
+
* @param array $tree
|
256 |
+
* @return string
|
257 |
+
*/
|
258 |
+
function getMenuAsJS($tree){
|
259 |
+
return $this->json_encode($tree);
|
260 |
+
}
|
261 |
+
|
262 |
+
/**
|
263 |
+
* WPMenuEditor::menu2assoc()
|
264 |
+
* Convert a WP menu structure to an associative array
|
265 |
+
*
|
266 |
+
* @param array $item An element of the $menu array
|
267 |
+
* @param integer $pos The position (index) of the menu item
|
268 |
+
* @return array
|
269 |
+
*/
|
270 |
+
function menu2assoc($item, $pos=0){
|
271 |
+
$item = array(
|
272 |
+
'page_title' => $item[3],
|
273 |
+
'menu_title' => $item[0],
|
274 |
+
'access_level' => $item[1],
|
275 |
+
'file' => $item[2],
|
276 |
+
'css_class' => $item[4],
|
277 |
+
'hookname' => (isset($item[5])?$item[5]:''), //ID
|
278 |
+
'icon_url' => (isset($item[6])?$item[6]:''),
|
279 |
+
'position' => $pos,
|
280 |
+
);
|
281 |
+
return $item;
|
282 |
+
}
|
283 |
+
|
284 |
+
/**
|
285 |
+
* WPMenuEditor::submenu2assoc()
|
286 |
+
* Converts a WP submenu structure to an associative array
|
287 |
+
*
|
288 |
+
* @param array $item An element of the $submenu array
|
289 |
+
* @param integer $pos The position (index) of that element
|
290 |
+
* @return
|
291 |
+
*/
|
292 |
+
function submenu2assoc($item, $pos=0){
|
293 |
+
$item = array(
|
294 |
+
'menu_title' => $item[0],
|
295 |
+
'access_level' => $item[1],
|
296 |
+
'file' => $item[2],
|
297 |
+
'page_title' => (isset($item[3])?$item[3]:''),
|
298 |
+
'position' => $pos,
|
299 |
+
);
|
300 |
+
return $item;
|
301 |
+
}
|
302 |
+
|
303 |
+
/**
|
304 |
+
* WPMenuEditor::build_lookups()
|
305 |
+
* Populate lookup arrays with default values from $menu and $submenu. Used later to merge
|
306 |
+
* a custom menu with the native WordPress menu structure somewhat gracefully.
|
307 |
+
*
|
308 |
+
* @param array $menu
|
309 |
+
* @param array $submenu
|
310 |
+
* @return array An array with two elements containing menu and submenu defaults.
|
311 |
+
*/
|
312 |
+
function build_lookups($menu, $submenu){
|
313 |
+
//Process the top menu
|
314 |
+
$menu_defaults = array();
|
315 |
+
foreach($menu as $pos => $item){
|
316 |
+
$item = $this->menu2assoc($item, $pos);
|
317 |
+
if ($item['file'] != '') { //skip separators (empty menus)
|
318 |
+
$menu_defaults[$item['file']] = $item; //index by filename
|
319 |
+
}
|
320 |
+
}
|
321 |
+
|
322 |
+
//Process the submenu
|
323 |
+
$submenu_defaults = array();
|
324 |
+
foreach($submenu as $parent => $items){
|
325 |
+
foreach($items as $pos => $item){
|
326 |
+
$item = $this->submenu2assoc($item, $pos);
|
327 |
+
//save the default parent menu
|
328 |
+
$item['parent'] = $parent;
|
329 |
+
$submenu_defaults[$item['file']] = $item; //index by filename
|
330 |
+
}
|
331 |
+
}
|
332 |
+
|
333 |
+
return array($menu_defaults, $submenu_defaults);
|
334 |
+
}
|
335 |
+
|
336 |
+
/**
|
337 |
+
* WPMenuEditor::menu_merge()
|
338 |
+
* Merge $menu and $submenu into the $tree. Adds/replaces defaults, inserts new items
|
339 |
+
* and marks missing items as such.
|
340 |
+
*
|
341 |
+
* @param array $tree A menu in plugin's internal form
|
342 |
+
* @param array $menu WordPress menu structure
|
343 |
+
* @param array $submenu WordPress submenu structure
|
344 |
+
* @return array Updated menu tree
|
345 |
+
*/
|
346 |
+
function menu_merge($tree, $menu, $submenu){
|
347 |
+
list($menu_defaults, $submenu_defaults) = $this->build_lookups($menu, $submenu);
|
348 |
+
|
349 |
+
//Iterate over all menus and submenus and look up default values
|
350 |
+
foreach ($tree as $topfile => &$topmenu){
|
351 |
+
|
352 |
+
//Is this menu present in the default WP menu?
|
353 |
+
if (isset($menu_defaults[$topfile])){
|
354 |
+
//Yes, load defaults from that item
|
355 |
+
$topmenu['defaults'] = $menu_defaults[$topfile];
|
356 |
+
//Note that the original item was used
|
357 |
+
$menu_defaults[$topfile]['used'] = true;
|
358 |
+
} else {
|
359 |
+
//Record the menu as missing, unless it's a menu separator
|
360 |
+
if ( empty($topmenu['separator']) /*strpos($topfile, 'separator_') !== false*/ )
|
361 |
+
$topmenu['missing'] = true;
|
362 |
+
}
|
363 |
+
|
364 |
+
if (is_array($topmenu['items'])) {
|
365 |
+
//Iterate over submenu items
|
366 |
+
foreach ($topmenu['items'] as $file => &$item){
|
367 |
+
//Is this item present in the default WP menu?
|
368 |
+
if (isset($submenu_defaults[$file])){
|
369 |
+
//Yes, load defaults from that item
|
370 |
+
$item['defaults'] = $submenu_defaults[$file];
|
371 |
+
$submenu_defaults[$file]['used'] = true;
|
372 |
+
} else {
|
373 |
+
//Record as missing
|
374 |
+
$item['missing'] = true;
|
375 |
+
}
|
376 |
+
}
|
377 |
+
}
|
378 |
+
}
|
379 |
+
|
380 |
+
//If we don't unset these they will fuck up the next two loops where the same names are used.
|
381 |
+
unset($topmenu);
|
382 |
+
unset($item);
|
383 |
+
|
384 |
+
//Note : Now we have some items marked as missing, and some items in lookup arrays
|
385 |
+
//that are not marked as used. The missing items are handled elsewhere (e.g. tree2wp()),
|
386 |
+
//but lets merge in the unused items now.
|
387 |
+
|
388 |
+
//Find and merge unused toplevel menus
|
389 |
+
foreach ($menu_defaults as $topfile => $topmenu){
|
390 |
+
if ( !empty($topmenu['used']) ) continue;
|
391 |
+
|
392 |
+
//Found an unused item. Build the tree entry.
|
393 |
+
$entry = $this->blank_menu;
|
394 |
+
$entry['defaults'] = $topmenu;
|
395 |
+
$entry['items'] = array(); //prepare a place for menu items, if any.
|
396 |
+
//Note that this item is unused
|
397 |
+
$entry['unused'] = true;
|
398 |
+
//Add the new entry to the menu tree
|
399 |
+
$tree[$topfile] = $entry;
|
400 |
+
}
|
401 |
+
unset($topmenu);
|
402 |
+
|
403 |
+
//Find and merge submenu items
|
404 |
+
foreach($submenu_defaults as $file => $item){
|
405 |
+
if ( !empty($item['used']) ) continue;
|
406 |
+
//Found an unused item. Build an entry and attach it under the default toplevel menu.
|
407 |
+
$entry = $this->blank_item;
|
408 |
+
$entry['defaults'] = $item;
|
409 |
+
//Note that this item is unused
|
410 |
+
$entry['unused'] = true;
|
411 |
+
|
412 |
+
//Check if the toplevel menu exists
|
413 |
+
if (isset($tree[$item['parent']])) {
|
414 |
+
//Okay, insert the item.
|
415 |
+
$tree[$item['parent']]['items'][$item['file']] = $entry;
|
416 |
+
} else {
|
417 |
+
//Ooops? This should never happen. Some kind of inconsistency?
|
418 |
+
}
|
419 |
+
}
|
420 |
+
|
421 |
+
//Resort the tree to ensure the found items are in the right spots
|
422 |
+
uasort($tree, array(&$this, 'compare_position'));
|
423 |
+
//Resort all submenus as well
|
424 |
+
foreach ($tree as $topfile => &$topmenu){
|
425 |
+
if (!empty($topmenu['items'])){
|
426 |
+
uasort($topmenu['items'], array(&$this, 'compare_position'));
|
427 |
+
}
|
428 |
+
}
|
429 |
+
|
430 |
+
return $tree;
|
431 |
+
}
|
432 |
+
|
433 |
+
/**
|
434 |
+
* WPMenuEditor::wp2tree()
|
435 |
+
* Convert the WP menu structure to the internal representation. All properties set as defaults.
|
436 |
+
*
|
437 |
+
* @param array $menu
|
438 |
+
* @param array $submenu
|
439 |
+
* @return array
|
440 |
+
*/
|
441 |
+
function wp2tree($menu, $submenu){
|
442 |
+
$tree = array();
|
443 |
+
$separator_count = 0;
|
444 |
+
foreach ($menu as $pos => $item){
|
445 |
+
//Is this a separator?
|
446 |
+
if ($item[2] == ''){
|
447 |
+
//Yes. Most properties are unset for separators.
|
448 |
+
$tree['separator_'.$separator_count.'_'] = array(
|
449 |
+
'page_title' => null,
|
450 |
+
'menu_title' => null,
|
451 |
+
'access_level' => null,
|
452 |
+
'file' => null,
|
453 |
+
'css_class' => null,
|
454 |
+
'hookname' => null,
|
455 |
+
'icon_url' => null,
|
456 |
+
'position' => null,
|
457 |
+
'defaults' => $this->menu2assoc($item, $pos),
|
458 |
+
'separator' => true,
|
459 |
+
);
|
460 |
+
$separator_count++;
|
461 |
+
} else {
|
462 |
+
//No, a normal menu item
|
463 |
+
$tree[$item[2]] = array(
|
464 |
+
'page_title' => null,
|
465 |
+
'menu_title' => null,
|
466 |
+
'access_level' => null,
|
467 |
+
'file' => null,
|
468 |
+
'css_class' => null,
|
469 |
+
'hookname' => null,
|
470 |
+
'icon_url' => null,
|
471 |
+
'position' => null,
|
472 |
+
'items' => array(),
|
473 |
+
'defaults' => $this->menu2assoc($item, $pos),
|
474 |
+
);
|
475 |
+
}
|
476 |
+
}
|
477 |
+
|
478 |
+
//Attach all submenu items
|
479 |
+
foreach($submenu as $parent=>$items){
|
480 |
+
foreach($items as $pos=>$item){
|
481 |
+
//Add this item under the parent
|
482 |
+
$tree[$parent]['items'][$item[2]] = array(
|
483 |
+
'menu_title' => null,
|
484 |
+
'access_level' => null,
|
485 |
+
'file' => null,
|
486 |
+
'page_title' => null,
|
487 |
+
'position' => null,
|
488 |
+
'defaults' => $this->submenu2assoc($item, $pos),
|
489 |
+
);
|
490 |
+
}
|
491 |
+
}
|
492 |
+
|
493 |
+
return $tree;
|
494 |
+
}
|
495 |
+
|
496 |
+
/**
|
497 |
+
* WPMenuEditor::apply_defaults()
|
498 |
+
* Sets all undefined fields to the default value
|
499 |
+
*
|
500 |
+
* @param array $item Menu item in the plugin's internal form
|
501 |
+
* @return array
|
502 |
+
*/
|
503 |
+
function apply_defaults($item){
|
504 |
+
foreach($item as $key => $value){
|
505 |
+
//Is the field set?
|
506 |
+
if ($value === null){
|
507 |
+
//Use default, if available
|
508 |
+
if (isset($item['defaults']) && isset($item['defaults'][$key])){
|
509 |
+
$item[$key] = $item['defaults'][$key];
|
510 |
+
}
|
511 |
+
}
|
512 |
+
}
|
513 |
+
return $item;
|
514 |
+
}
|
515 |
+
|
516 |
+
/**
|
517 |
+
* WPMenuEditor::compare_position()
|
518 |
+
* Custom comparison function that compares menu items based on their position in the menu.
|
519 |
+
*
|
520 |
+
* @param array $a
|
521 |
+
* @param array $b
|
522 |
+
* @return int
|
523 |
+
*/
|
524 |
+
function compare_position($a, $b){
|
525 |
+
if ($a['position']!==null) {
|
526 |
+
$p1 = $a['position'];
|
527 |
+
} else {
|
528 |
+
if ( isset($a['defaults']['position']) ){
|
529 |
+
$p1 = $a['defaults']['position'];
|
530 |
+
} else {
|
531 |
+
$p1 = 0;
|
532 |
+
}
|
533 |
+
}
|
534 |
+
|
535 |
+
if ($b['position']!==null) {
|
536 |
+
$p2 = $b['position'];
|
537 |
+
} else {
|
538 |
+
if ( isset($b['defaults']['position']) ){
|
539 |
+
$p2 = $b['defaults']['position'];
|
540 |
+
} else {
|
541 |
+
$p2 = 0;
|
542 |
+
}
|
543 |
+
}
|
544 |
+
|
545 |
+
return $p1 - $p2;
|
546 |
+
}
|
547 |
+
|
548 |
+
/**
|
549 |
+
* WPMenuEditor::tree2wp()
|
550 |
+
* Convert internal menu representation to the form used by WP.
|
551 |
+
*
|
552 |
+
* @param array $tree
|
553 |
+
* @return array $menu and $submenu
|
554 |
+
*/
|
555 |
+
function tree2wp($tree){
|
556 |
+
//Sort the menu by position
|
557 |
+
uasort($tree, array(&$this, 'compare_position'));
|
558 |
+
|
559 |
+
//Prepare the top menu
|
560 |
+
$menu = array();
|
561 |
+
foreach ($tree as &$topmenu){
|
562 |
+
//Skip missing entries -- disabled to allow user-created menus
|
563 |
+
//if (isset($topmenu['missing']) && $topmenu['missing']) continue;
|
564 |
+
//Skip hidden entries
|
565 |
+
if (!empty($topmenu['hidden'])) continue;
|
566 |
+
//Build the WP item structure, using defaults where necessary
|
567 |
+
$topmenu = $this->apply_defaults($topmenu);
|
568 |
+
$menu[] = array(
|
569 |
+
$topmenu['menu_title'],
|
570 |
+
$topmenu['access_level'],
|
571 |
+
$topmenu['file'],
|
572 |
+
$topmenu['page_title'],
|
573 |
+
$topmenu['css_class'],
|
574 |
+
$topmenu['hookname'], //ID
|
575 |
+
$topmenu['icon_url']
|
576 |
+
);
|
577 |
+
}
|
578 |
+
|
579 |
+
//Prepare the submenu
|
580 |
+
$submenu = array();
|
581 |
+
foreach ($tree as $x){
|
582 |
+
if (!isset($x['items'])) continue; //skip menus without items (usually separators)
|
583 |
+
$items = $x['items'];
|
584 |
+
//Sort by position
|
585 |
+
uasort($items, array(&$this, 'compare_position'));
|
586 |
+
foreach ($items as $item) {
|
587 |
+
//Skip hidden items
|
588 |
+
if (!empty($item['hidden'])) {
|
589 |
+
continue;
|
590 |
+
}
|
591 |
+
|
592 |
+
$item = $this->apply_defaults($item);
|
593 |
+
$submenu[$x['file']][] = array(
|
594 |
+
$item['menu_title'],
|
595 |
+
$item['access_level'],
|
596 |
+
$item['file'],
|
597 |
+
$item['page_title'],
|
598 |
+
);
|
599 |
+
}
|
600 |
+
}
|
601 |
+
|
602 |
+
return array($menu, $submenu);
|
603 |
+
}
|
604 |
+
|
605 |
+
/**
|
606 |
+
* WPMenuEditor::is_wp27()
|
607 |
+
* Check if running WordPress 2.7 or later (unused)
|
608 |
+
*
|
609 |
+
* @return bool
|
610 |
+
*/
|
611 |
+
function is_wp27(){
|
612 |
+
global $wp_version;
|
613 |
+
return function_exists('register_uninstall_hook');
|
614 |
+
}
|
615 |
+
} //class
|
616 |
+
|
617 |
+
?>
|
menu-editor.css
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
width: 120px;
|
17 |
+
margin: 4px;
|
18 |
+
padding: 4px;
|
19 |
+
}
|
20 |
+
|
21 |
+
.ws_container {
|
22 |
+
display: block;
|
23 |
+
width: 290px;
|
24 |
+
padding : 3px;
|
25 |
+
margin: 2px;
|
26 |
+
border: 1px solid #a9badb;
|
27 |
+
background-color: #bdd3ff;
|
28 |
+
}
|
29 |
+
|
30 |
+
.ws_active {
|
31 |
+
background-color : #8eb0f1 !important; /* make sure this overrides ws_menu_separator */
|
32 |
+
}
|
33 |
+
|
34 |
+
#ws_menu_box {
|
35 |
+
}
|
36 |
+
|
37 |
+
#ws_submenu_box {
|
38 |
+
}
|
39 |
+
|
40 |
+
.ws_menu {
|
41 |
+
}
|
42 |
+
|
43 |
+
.ws_menu_separator {
|
44 |
+
background-color: #f0f0f0;
|
45 |
+
}
|
46 |
+
|
47 |
+
.ws_submenu {
|
48 |
+
background-color: #f0fafa;
|
49 |
+
min-height: 2em;
|
50 |
+
}
|
51 |
+
|
52 |
+
|
53 |
+
.ws_item_head {
|
54 |
+
padding-left: 2px;
|
55 |
+
padding-top: 2px;
|
56 |
+
padding-bottom: 2px;
|
57 |
+
cursor: default;
|
58 |
+
}
|
59 |
+
|
60 |
+
.ws_item_title {
|
61 |
+
}
|
62 |
+
|
63 |
+
.ws_edit_link {
|
64 |
+
float: right;
|
65 |
+
margin-right: 0px;
|
66 |
+
cursor: pointer;
|
67 |
+
display:block;
|
68 |
+
height: 18px;
|
69 |
+
width: 40px;
|
70 |
+
background-image: url('images/bullet_arrow_down2.png');
|
71 |
+
background-repeat: no-repeat;
|
72 |
+
background-position: center;
|
73 |
+
}
|
74 |
+
a.ws_edit_link:hover {
|
75 |
+
background-color: #ffffd0;
|
76 |
+
background-image: url('images/bullet_arrow_down2.png');
|
77 |
+
background-repeat: no-repeat;
|
78 |
+
background-position: center;
|
79 |
+
}
|
80 |
+
|
81 |
+
.ws_edit_link_expanded {
|
82 |
+
background-color: #ffffd0;
|
83 |
+
border-bottom: none;
|
84 |
+
border-color: #ffffd0;
|
85 |
+
background-image: url('images/bullet_arrow_down2.png');
|
86 |
+
background-repeat: no-repeat;
|
87 |
+
background-position: center;
|
88 |
+
}
|
89 |
+
|
90 |
+
/* style for items not present in the default menu - these are usually user-created items */
|
91 |
+
.ws_missing {
|
92 |
+
background-image: url('images/page_white_add.png');
|
93 |
+
background-repeat: no-repeat;
|
94 |
+
background-position: 232px 5px;
|
95 |
+
}
|
96 |
+
|
97 |
+
/* style for hidden items */
|
98 |
+
.ws_hidden {
|
99 |
+
background-image: url('images/plugin_disabled.png');
|
100 |
+
background-repeat: no-repeat;
|
101 |
+
background-position: 232px 5px;
|
102 |
+
}
|
103 |
+
|
104 |
+
/* style for unused items - those that are in the default menu but not in the custom one */
|
105 |
+
.ws_unused {
|
106 |
+
background-image: url('images/bullet_error.png');
|
107 |
+
background-repeat: no-repeat;
|
108 |
+
background-position: 232px 5px;
|
109 |
+
}
|
110 |
+
|
111 |
+
.ws_item {
|
112 |
+
}
|
113 |
+
|
114 |
+
.ws_editbox {
|
115 |
+
display: block;
|
116 |
+
background-color: #ffffd0;
|
117 |
+
padding: 4px;
|
118 |
+
}
|
119 |
+
|
120 |
+
/* the reset-to-default button */
|
121 |
+
.ws_reset_button {
|
122 |
+
font-size: smaller;
|
123 |
+
margin : 1px;
|
124 |
+
cursor: pointer;
|
125 |
+
}
|
126 |
+
|
127 |
+
.ws_editbox input {
|
128 |
+
width: 220px;
|
129 |
+
}
|
130 |
+
|
131 |
+
.ws_input_default {
|
132 |
+
color: gray;
|
133 |
+
}
|
134 |
+
|
135 |
+
.ws_edit_field {
|
136 |
+
margin-bottom: 8px;
|
137 |
+
}
|
138 |
+
|
139 |
+
.ws_toolbar {
|
140 |
+
display: block;
|
141 |
+
width: 290px;
|
142 |
+
height: 32px;
|
143 |
+
}
|
144 |
+
|
145 |
+
.ws_button {
|
146 |
+
display: block;
|
147 |
+
margin: 2px;
|
148 |
+
padding: 4px;
|
149 |
+
border: 1px solid #c0c0e0;
|
150 |
+
float: left;
|
151 |
+
}
|
152 |
+
|
153 |
+
a.ws_button:hover {
|
154 |
+
background-color: #d0e0ff;
|
155 |
+
}
|
menu-editor.js
ADDED
@@ -0,0 +1,586 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
58 |
+
|
59 |
+
//The "Default" button : Reset to default value when clicked
|
60 |
+
$('.ws_reset_button').click(function () {
|
61 |
+
//Find the related input field
|
62 |
+
var field = $(this).siblings('input');
|
63 |
+
if (field.length > 0) {
|
64 |
+
//Set the value to the default
|
65 |
+
field.val(field.attr('default'));
|
66 |
+
field.addClass('ws_input_default');
|
67 |
+
//Trigget the change event to ensure consistency
|
68 |
+
field.change();
|
69 |
+
}
|
70 |
+
});
|
71 |
+
|
72 |
+
//When a field is edited, change it's appearance if it's contents don't match the default value.
|
73 |
+
$('.ws_edit_field input[type="text"]').change(function () {
|
74 |
+
if ( $(this).attr('default') != $(this).val() ) {
|
75 |
+
$(this).removeClass('ws_input_default');
|
76 |
+
}
|
77 |
+
|
78 |
+
//If the changed field is the menu title, update the header
|
79 |
+
if ( $(this).parent().attr('field_name')=='menu_title' ){
|
80 |
+
$(this).parent().parent().parent().find('.ws_item_title').html($(this).val()+' ');
|
81 |
+
}
|
82 |
+
});
|
83 |
+
}
|
84 |
+
|
85 |
+
function outputTopMenu(menu, filename, ind){
|
86 |
+
id = 'topmenu-'+ind;
|
87 |
+
submenu_id = 'submenu-'+ind;
|
88 |
+
|
89 |
+
//menu = menu_obj[filename];
|
90 |
+
|
91 |
+
var subclass = '';
|
92 |
+
//Apply subclasses based on the item's state
|
93 |
+
if ( menu.separator /*(!menu.defaults.menu_title) && (!menu.menu_title)*/ ) {
|
94 |
+
subclass = subclass + ' ws_menu_separator';
|
95 |
+
}
|
96 |
+
if (menu.missing) {
|
97 |
+
subclass = subclass + ' ws_missing';
|
98 |
+
}
|
99 |
+
if (menu.hidden) {
|
100 |
+
subclass = subclass + ' ws_hidden';
|
101 |
+
}
|
102 |
+
if (menu.unused) {
|
103 |
+
subclass = subclass + ' ws_unused';
|
104 |
+
}
|
105 |
+
|
106 |
+
var s = '<div id="'+id+'" class="ws_container ws_menu '+subclass+'" submenu_id="'+submenu_id+'">'+
|
107 |
+
'<div class="ws_item_head">'+
|
108 |
+
'<a class="ws_edit_link"> </a>'+
|
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 |
+
$('#ws_menu_box').append(s);
|
117 |
+
//Create a container for menu items, even if there are none
|
118 |
+
$('#ws_submenu_box').append('<div class="ws_submenu" id="'+submenu_id+'" style="display:none;"></div>');
|
119 |
+
|
120 |
+
//Only show menus that have items.
|
121 |
+
//Skip arrays (with a length) because filled menus are encoded as custom objects ().
|
122 |
+
if (menu.items && (typeof menu.items != 'Array')){
|
123 |
+
var i = 0;
|
124 |
+
for (var item_file in menu.items){
|
125 |
+
outputMenuEntry(menu.items[item_file], i, submenu_id);
|
126 |
+
i++;
|
127 |
+
}
|
128 |
+
}
|
129 |
+
}
|
130 |
+
|
131 |
+
function outputMenuEntry(entry, ind, parent){
|
132 |
+
if (!entry.defaults) return;
|
133 |
+
|
134 |
+
var subclass = '';
|
135 |
+
//Apply subclasses based on the item's state
|
136 |
+
if (entry.missing) {
|
137 |
+
subclass = subclass + ' ws_missing';
|
138 |
+
}
|
139 |
+
if (entry.hidden) {
|
140 |
+
subclass = subclass + ' ws_hidden';
|
141 |
+
}
|
142 |
+
if (entry.unused) {
|
143 |
+
subclass = subclass + ' ws_unused';
|
144 |
+
}
|
145 |
+
|
146 |
+
var item = $('#'+parent).append('<div class="ws_container ws_item '+subclass+'">'+
|
147 |
+
'<div class="ws_item_head">'+
|
148 |
+
'<a class="ws_edit_link"> </a>'+
|
149 |
+
'<span class="ws_item_title">'+
|
150 |
+
((entry.menu_title!=null)?entry.menu_title:entry.defaults.menu_title)+
|
151 |
+
' </span>'+
|
152 |
+
'</div>'+
|
153 |
+
'<div class="ws_editbox" style="display:none;">'+buildEditboxFields(entry)+'</div>'+
|
154 |
+
'<div>');
|
155 |
+
}
|
156 |
+
|
157 |
+
function buildEditboxField(entry, field_name, field_caption){
|
158 |
+
if (entry[field_name]===undefined) {
|
159 |
+
return ''; //skip fields this entry doesn't have
|
160 |
+
}
|
161 |
+
|
162 |
+
return '<div class="ws_edit_field" field_name="'+field_name+'">' + (field_caption) + '<br />' +
|
163 |
+
'<input type="text" value="'+escapeJS((entry[field_name]!=null)?entry[field_name]:entry.defaults[field_name])+
|
164 |
+
'" default=\''+escapeJS(entry.defaults[field_name])+'\''+
|
165 |
+
' class="'+((entry[field_name]==null)?'ws_input_default':'')+'">'+
|
166 |
+
'<span class="ws_reset_button">[default]</span></div>';
|
167 |
+
}
|
168 |
+
|
169 |
+
function buildEditboxFields(entry){
|
170 |
+
var fields = {
|
171 |
+
'menu_title' : "Menu title",
|
172 |
+
'page_title' : "Page title",
|
173 |
+
'access_level' : 'Access level',
|
174 |
+
'file' : 'File',
|
175 |
+
'css_class' : 'CSS class',
|
176 |
+
'hookname' : 'CSS ID',
|
177 |
+
'icon_url' : 'Icon URL'
|
178 |
+
};
|
179 |
+
var s = '';
|
180 |
+
|
181 |
+
for (var field_name in fields){
|
182 |
+
s = s + buildEditboxField(entry, field_name, fields[field_name]);
|
183 |
+
}
|
184 |
+
return s;
|
185 |
+
}
|
186 |
+
|
187 |
+
//Encode the current menu structure as JSON
|
188 |
+
function encodeMenuAsJSON(){
|
189 |
+
var data = {};
|
190 |
+
var separator_count = 0;
|
191 |
+
var menu_position = 0;
|
192 |
+
|
193 |
+
//Iterate over all menus
|
194 |
+
$('#ws_menu_box .ws_menu').each(function(i) {
|
195 |
+
|
196 |
+
var menu_obj = {};
|
197 |
+
menu_obj.defaults = {};
|
198 |
+
|
199 |
+
menu_position++;
|
200 |
+
menu_obj.position = menu_position;
|
201 |
+
menu_obj.defaults.position = menu_position; //the real default value will later overwrite this
|
202 |
+
|
203 |
+
var filename = $(this).find('.ws_edit_field[field_name="file"] input').val();
|
204 |
+
//Check if this is a separator
|
205 |
+
if (filename==''){
|
206 |
+
filename = 'separator_'+separator_count+'_';
|
207 |
+
menu_obj.separator = true;
|
208 |
+
separator_count++;
|
209 |
+
}
|
210 |
+
|
211 |
+
//Iterate over all fields of the menu
|
212 |
+
$(this).find('.ws_edit_field').each(function() {
|
213 |
+
//Get the name of this field
|
214 |
+
field_name = $(this).attr('field_name');
|
215 |
+
//Skip if unnamed
|
216 |
+
if (!field_name) return true;
|
217 |
+
|
218 |
+
input_box = $(this).find('input');
|
219 |
+
//Save null if default used, custom value otherwise
|
220 |
+
if (input_box.hasClass('ws_input_default')){
|
221 |
+
menu_obj[field_name] = null;
|
222 |
+
} else {
|
223 |
+
menu_obj[field_name] = input_box.val();
|
224 |
+
}
|
225 |
+
menu_obj.defaults[field_name]=input_box.attr('default');
|
226 |
+
|
227 |
+
});
|
228 |
+
//Check if the menu is hidden
|
229 |
+
if ($(this).hasClass('ws_hidden')){
|
230 |
+
menu_obj['hidden'] = true;
|
231 |
+
}
|
232 |
+
|
233 |
+
menu_obj.items = {};
|
234 |
+
|
235 |
+
var item_position = 0;
|
236 |
+
|
237 |
+
//Iterate over the menu's items, if any
|
238 |
+
$('#'+$(this).attr('submenu_id')).find('.ws_item').each(function (i) {
|
239 |
+
var filename = $(this).find('.ws_edit_field[field_name="file"] input').val();
|
240 |
+
|
241 |
+
var item = {};
|
242 |
+
item.defaults = {};
|
243 |
+
|
244 |
+
//Save the position data (probably not all that useful)
|
245 |
+
item_position++;
|
246 |
+
item.position = item_position;
|
247 |
+
item.defaults.position = item_position;
|
248 |
+
|
249 |
+
//Iterate over all fields of the item
|
250 |
+
$(this).find('.ws_edit_field').each(function() {
|
251 |
+
//Get the name of this field
|
252 |
+
field_name = $(this).attr('field_name');
|
253 |
+
//Skip if unnamed
|
254 |
+
if (!field_name) return true;
|
255 |
+
|
256 |
+
input_box = $(this).find('input');
|
257 |
+
//Save null if default used, custom value otherwise
|
258 |
+
if (input_box.hasClass('ws_input_default')){
|
259 |
+
item[field_name] = null;
|
260 |
+
} else {
|
261 |
+
item[field_name] = input_box.val();
|
262 |
+
}
|
263 |
+
item.defaults[field_name]=input_box.attr('default');
|
264 |
+
|
265 |
+
});
|
266 |
+
//Check if the item is hidden
|
267 |
+
if ($(this).hasClass('ws_hidden')){
|
268 |
+
item.hidden = true;
|
269 |
+
}
|
270 |
+
//Save the item in the parent menu
|
271 |
+
menu_obj.items[filename] = item;
|
272 |
+
});
|
273 |
+
//*/
|
274 |
+
|
275 |
+
//Attach the menu to the main struct
|
276 |
+
data[filename] = menu_obj;
|
277 |
+
|
278 |
+
});
|
279 |
+
|
280 |
+
return $.toJSON(data);
|
281 |
+
}
|
282 |
+
|
283 |
+
var menu_in_clipboard = null;
|
284 |
+
var submenu_in_clipboard = null;
|
285 |
+
var item_in_clipboard = null;
|
286 |
+
var ws_paste_count = 0;
|
287 |
+
|
288 |
+
$(document).ready(function(){
|
289 |
+
|
290 |
+
//Show the default menu
|
291 |
+
outputWpMenu(customMenu);
|
292 |
+
|
293 |
+
//Make the top menu box sortable (we only need to do this once)
|
294 |
+
$('#ws_menu_box').sortable({
|
295 |
+
items: '> .ws_container',
|
296 |
+
cursor: 'move',
|
297 |
+
dropOnEmpty: true,
|
298 |
+
});
|
299 |
+
|
300 |
+
//===== Toolbar buttons =======
|
301 |
+
//Show/Hide menu
|
302 |
+
$('#ws_hide_menu').click(function () {
|
303 |
+
//Get the selected menu
|
304 |
+
var selection = $('#ws_menu_box .ws_active');
|
305 |
+
if (!selection.length) return;
|
306 |
+
|
307 |
+
//Mark the menu as hidden
|
308 |
+
selection.toggleClass('ws_hidden');
|
309 |
+
//Also mark all of it's submenus as hidden/visible
|
310 |
+
if (selection.hasClass('ws_hidden')){
|
311 |
+
$('#' + selection.attr('submenu_id') + ' .ws_item').addClass('ws_hidden');
|
312 |
+
} else {
|
313 |
+
$('#' + selection.attr('submenu_id') + ' .ws_item').removeClass('ws_hidden');
|
314 |
+
}
|
315 |
+
});
|
316 |
+
|
317 |
+
//Delete menu
|
318 |
+
$('#ws_delete_menu').click(function () {
|
319 |
+
//Get the selected menu
|
320 |
+
var selection = $('#ws_menu_box .ws_active');
|
321 |
+
if (!selection.length) return;
|
322 |
+
|
323 |
+
if (confirm('Are you sure you want to delete this menu?')){
|
324 |
+
//Delete the submenu first
|
325 |
+
$('#' + selection.attr('submenu_id')).remove();
|
326 |
+
//Delete the menu
|
327 |
+
selection.remove();
|
328 |
+
}
|
329 |
+
});
|
330 |
+
|
331 |
+
//Copy menu
|
332 |
+
$('#ws_copy_menu').click(function () {
|
333 |
+
//Get the selected menu
|
334 |
+
var selection = $('#ws_menu_box .ws_active');
|
335 |
+
if (!selection.length) return;
|
336 |
+
|
337 |
+
//Store a copy in clipboard
|
338 |
+
menu_in_clipboard = selection.clone(true); //just like that
|
339 |
+
menu_in_clipboard.removeClass('ws_active');
|
340 |
+
submenu_in_clipboard = $('#'+selection.attr('submenu_id')).clone(true);
|
341 |
+
});
|
342 |
+
|
343 |
+
//Cut menu
|
344 |
+
$('#ws_cut_menu').click(function () {
|
345 |
+
//Get the selected menu
|
346 |
+
var selection = $('#ws_menu_box .ws_active');
|
347 |
+
if (!selection.length) return;
|
348 |
+
|
349 |
+
//Store a copy of both menu and it's submenu in clipboard
|
350 |
+
menu_in_clipboard = selection.removeClass('ws_active').clone(true);
|
351 |
+
menu_in_clipboard.removeClass('ws_active');
|
352 |
+
submenu_in_clipboard = $('#'+selection.attr('submenu_id')).clone(true);
|
353 |
+
//Remove the original menu and submenu
|
354 |
+
selection.remove();
|
355 |
+
$('#'+selection.attr('submenu_id')).remove;
|
356 |
+
});
|
357 |
+
|
358 |
+
//Paste menu
|
359 |
+
$('#ws_paste_menu').click(function () {
|
360 |
+
//Check if anything has been copied/cut
|
361 |
+
if (!menu_in_clipboard) return;
|
362 |
+
//Get the selected menu
|
363 |
+
var selection = $('#ws_menu_box .ws_active');
|
364 |
+
|
365 |
+
ws_paste_count++;
|
366 |
+
|
367 |
+
//Clone new objects from the virtual clipboard
|
368 |
+
var new_menu = menu_in_clipboard.clone(true);
|
369 |
+
var new_submenu = submenu_in_clipboard.clone(true);
|
370 |
+
//Close submenu editboxes
|
371 |
+
new_submenu.find('.ws_editbox').hide();
|
372 |
+
|
373 |
+
//The cloned menu must have a unique file name, unless it's a separator
|
374 |
+
if (!new_menu.hasClass('ws_menu_separator')) {
|
375 |
+
new_menu.find('.ws_edit_field[field_name="file"] input').val('custom_menu_'+ws_paste_count);
|
376 |
+
}
|
377 |
+
|
378 |
+
//The cloned submenu needs a unique ID (could be improved)
|
379 |
+
new_submenu.attr('id', 'ws-pasted-obj-'+ws_paste_count);
|
380 |
+
new_menu.attr('submenu_id', 'ws-pasted-obj-'+ws_paste_count);
|
381 |
+
|
382 |
+
//Make the new submenu sortable
|
383 |
+
new_submenu.sortable({
|
384 |
+
items: '> .ws_container',
|
385 |
+
cursor: 'move',
|
386 |
+
dropOnEmpty: true,
|
387 |
+
});
|
388 |
+
|
389 |
+
if (selection.length > 0) {
|
390 |
+
//If a menu is selected add the pasted item after it
|
391 |
+
selection.after(new_menu);
|
392 |
+
} else {
|
393 |
+
//Otherwise add the pasted item at the end
|
394 |
+
$('#ws_menu_box').append(new_menu);
|
395 |
+
};
|
396 |
+
|
397 |
+
//Insert the submenu in the box, too
|
398 |
+
$('#ws_submenu_box').append(new_submenu);
|
399 |
+
|
400 |
+
new_menu.show();
|
401 |
+
new_submenu.hide();
|
402 |
+
});
|
403 |
+
|
404 |
+
//New menu
|
405 |
+
$('#ws_new_menu').click(function () {
|
406 |
+
ws_paste_count++;
|
407 |
+
|
408 |
+
//This is a hack.
|
409 |
+
//Clone another menu to use as a template
|
410 |
+
var menu = $('#ws_menu_box .ws_menu:first').clone(true);
|
411 |
+
//Also clone a submenu
|
412 |
+
var submenu = $('#' + menu.attr('submenu_id')).clone(true);
|
413 |
+
//Assign a new ID
|
414 |
+
submenu.attr('id', 'ws-new-submenu-'+ws_paste_count);
|
415 |
+
menu.attr('submenu_id', 'ws-new-submenu-'+ws_paste_count);
|
416 |
+
//Remove all items from the submenu
|
417 |
+
submenu.empty();
|
418 |
+
//Make the submenu sortable
|
419 |
+
submenu.sortable({
|
420 |
+
items: '> .ws_container',
|
421 |
+
cursor: 'move',
|
422 |
+
dropOnEmpty: true,
|
423 |
+
});
|
424 |
+
|
425 |
+
//Cleanup the menu's classes
|
426 |
+
menu.attr('class','ws_container ws_menu ws_missing');
|
427 |
+
|
428 |
+
|
429 |
+
var temp_id = 'custom_menu_'+ws_paste_count;
|
430 |
+
//Assign a stub title
|
431 |
+
menu.find('.ws_item_title').text('Custom Menu '+ws_paste_count);
|
432 |
+
//All fields start out set to defaults
|
433 |
+
menu.find('input').attr('default','').addClass('ws_input_default');
|
434 |
+
//Set all fields
|
435 |
+
menu.find('.ws_edit_field[field_name="page_title"] input').val('').attr('default','');
|
436 |
+
menu.find('.ws_edit_field[field_name="menu_title"] input').val('Custom Menu '+ws_paste_count).attr('default','Custom Menu '+ws_paste_count);
|
437 |
+
menu.find('.ws_edit_field[field_name="access_level"] input').val('read').attr('default','read');
|
438 |
+
menu.find('.ws_edit_field[field_name="file"] input').val(temp_id).attr('default',temp_id);
|
439 |
+
menu.find('.ws_edit_field[field_name="css_class"] input').val('menu-top').attr('default','menu-top');
|
440 |
+
menu.find('.ws_edit_field[field_name="icon_url"] input').val('images/generic.png').attr('default','images/generic.png');
|
441 |
+
menu.find('.ws_edit_field[field_name="hookname"] input').val(temp_id).attr('default',temp_id);
|
442 |
+
|
443 |
+
//The menus's editbox is always open
|
444 |
+
menu.find('.ws_editbox').show();
|
445 |
+
//Make sure the edit link is in the right state, too
|
446 |
+
menu.find('.ws_edit_link').addClass('ws_edit_link_expanded');
|
447 |
+
|
448 |
+
//Finally, insert the menu into the box
|
449 |
+
$('#ws_menu_box').append(menu);
|
450 |
+
//And insert the submenu
|
451 |
+
$('#ws_submenu_box').append(submenu);
|
452 |
+
});
|
453 |
+
|
454 |
+
//===== Item toolbar buttons =======
|
455 |
+
//Show/Hide item
|
456 |
+
$('#ws_hide_item').click(function () {
|
457 |
+
//Get the selected item
|
458 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
459 |
+
if (!selection.length) return;
|
460 |
+
|
461 |
+
//Mark the item as hidden
|
462 |
+
selection.toggleClass('ws_hidden');
|
463 |
+
});
|
464 |
+
|
465 |
+
//Delete menu
|
466 |
+
$('#ws_delete_item').click(function () {
|
467 |
+
//Get the selected menu
|
468 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
469 |
+
if (!selection.length) return;
|
470 |
+
|
471 |
+
if (confirm('Are you sure you want to delete this menu item?')){
|
472 |
+
//Delete the item
|
473 |
+
selection.remove();
|
474 |
+
}
|
475 |
+
});
|
476 |
+
|
477 |
+
//Copy item
|
478 |
+
$('#ws_copy_item').click(function () {
|
479 |
+
//Get the selected item
|
480 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
481 |
+
if (!selection.length) return;
|
482 |
+
|
483 |
+
//Store a copy in clipboard
|
484 |
+
item_in_clipboard = selection.clone(true); //just like that
|
485 |
+
item_in_clipboard.removeClass('ws_active');
|
486 |
+
});
|
487 |
+
|
488 |
+
//Cut item
|
489 |
+
$('#ws_cut_item').click(function () {
|
490 |
+
//Get the selected item
|
491 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
492 |
+
if (!selection.length) return;
|
493 |
+
|
494 |
+
//Store a the item in clipboard
|
495 |
+
item_in_clipboard = selection.clone(true);
|
496 |
+
item_in_clipboard.removeClass('ws_active');
|
497 |
+
//Remove the original item
|
498 |
+
selection.remove();
|
499 |
+
});
|
500 |
+
|
501 |
+
//Paste item
|
502 |
+
$('#ws_paste_item').click(function () {
|
503 |
+
//Check if anything has been copied/cut
|
504 |
+
if (!item_in_clipboard) return;
|
505 |
+
//Get the selected menu
|
506 |
+
var selection = $('#ws_submenu_box .ws_submenu:visible .ws_active');
|
507 |
+
|
508 |
+
ws_paste_count++;
|
509 |
+
|
510 |
+
//Clone a new object from the virtual clipboard
|
511 |
+
var new_item = item_in_clipboard.clone(true);
|
512 |
+
//The item's editbox is always closed
|
513 |
+
new_item.find('.ws_editbox').hide();
|
514 |
+
|
515 |
+
if (selection.length > 0) {
|
516 |
+
//If an item is selected add the pasted item after it
|
517 |
+
selection.after(new_item);
|
518 |
+
} else {
|
519 |
+
//Otherwise add the pasted item at the end
|
520 |
+
$('#ws_submenu_box .ws_submenu:visible').append(new_item);
|
521 |
+
};
|
522 |
+
|
523 |
+
new_item.show();
|
524 |
+
});
|
525 |
+
|
526 |
+
//New item
|
527 |
+
$('#ws_new_item').click(function () {
|
528 |
+
if ($('.ws_submenu:visible').length<1) return; //abort if no submenu visible
|
529 |
+
|
530 |
+
ws_paste_count++;
|
531 |
+
|
532 |
+
//Clone another item to use as a template (hack)
|
533 |
+
var menu = $('#ws_submenu_box .ws_item:first').clone(true);
|
534 |
+
|
535 |
+
//Cleanup the items's classes
|
536 |
+
menu.attr('class','ws_container ws_item ws_missing');
|
537 |
+
|
538 |
+
var temp_id = 'custom_item_'+ws_paste_count;
|
539 |
+
//Assign a stub title
|
540 |
+
menu.find('.ws_item_title').text('Custom Item '+ws_paste_count);
|
541 |
+
//All fields start out set to defaults
|
542 |
+
menu.find('input').attr('default','').addClass('ws_input_default');
|
543 |
+
//Set all fields
|
544 |
+
menu.find('.ws_edit_field[field_name="page_title"] input').val('').attr('default','');
|
545 |
+
menu.find('.ws_edit_field[field_name="menu_title"] input').val('Custom Item '+ws_paste_count).attr('default','Custom Item '+ws_paste_count);
|
546 |
+
menu.find('.ws_edit_field[field_name="access_level"] input').val('read').attr('default','read');
|
547 |
+
menu.find('.ws_edit_field[field_name="file"] input').val(temp_id).attr('default',temp_id);
|
548 |
+
|
549 |
+
//The items's editbox is always open
|
550 |
+
menu.find('.ws_editbox').show();
|
551 |
+
//Make sure the edit link is in the right state, too
|
552 |
+
menu.find('.ws_edit_link').addClass('ws_edit_link_expanded');
|
553 |
+
|
554 |
+
//Finally, insert the item into the box
|
555 |
+
$('.ws_submenu:visible').append(menu);
|
556 |
+
});
|
557 |
+
|
558 |
+
//==============================================
|
559 |
+
// Main buttons
|
560 |
+
//==============================================
|
561 |
+
|
562 |
+
//Save Changes - encode the current menu as JSON and save
|
563 |
+
$('#ws_save_menu').click(function () {
|
564 |
+
var data = encodeMenuAsJSON();
|
565 |
+
$('#ws_data').val(data);
|
566 |
+
$('#ws_main_form').submit();
|
567 |
+
});
|
568 |
+
|
569 |
+
//Load default menu - load the default WordPress menu
|
570 |
+
$('#ws_load_menu').click(function () {
|
571 |
+
if (confirm('Are you sure you want to load the default WordPress menu into the editor?')){
|
572 |
+
outputWpMenu(defaultMenu);
|
573 |
+
}
|
574 |
+
});
|
575 |
+
|
576 |
+
//Reset menu - re-load the custom menu = discards any changes made by user
|
577 |
+
$('#ws_reset_menu').click(function () {
|
578 |
+
if (confirm('Are you sure you want to reset the custom menu? Any unsaved changes will be lost!')){
|
579 |
+
outputWpMenu(customMenu);
|
580 |
+
}
|
581 |
+
});
|
582 |
+
|
583 |
+
});
|
584 |
+
|
585 |
+
|
586 |
+
})(jQuery);
|
menu-editor.php
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
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.1.5
|
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 |
+
?>
|
readme.txt
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== Admin Menu Editor ===
|
2 |
+
Contributors: whiteshadow
|
3 |
+
Donate link: http://w-shadow.com/
|
4 |
+
Tags: admin, dashboard, menu, security
|
5 |
+
Requires at least: 2.7.0
|
6 |
+
Tested up to: 2.9
|
7 |
+
Stable tag: 0.1.5
|
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 |
+
|
11 |
+
== Description ==
|
12 |
+
Admin Menu Editor lets you manually edit the Dashboard menu. You can reorder the menus, show/hide specific items, change access rights, and more.
|
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. For example, you could create a new menu leading directly to the "Pending comments" page.
|
21 |
+
|
22 |
+
**Known Issues**
|
23 |
+
|
24 |
+
* If you delete any of the default menus they will reappear after saving. This is by design.
|
25 |
+
* You can't use arbitrary URLs as menu targets because WordPress will automatically strip off the "http:/".
|
26 |
+
* A plugin's menu that is moved to a different submenu will not work unless you also include the parent file in the "File" field.
|
27 |
+
|
28 |
+
== Installation ==
|
29 |
+
|
30 |
+
To do a new installation of the plugin, please follow these steps
|
31 |
+
|
32 |
+
1. Download the admin-menu-editor.zip file to your local machine.
|
33 |
+
1. Unzip the file
|
34 |
+
1. Upload `admin-menu-editor` folder to the `/wp-content/plugins/` directory
|
35 |
+
1. Activate the plugin through the 'Plugins' menu in WordPress.
|
36 |
+
|
37 |
+
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.
|
38 |
+
|
39 |
+
To upgrade your installation
|
40 |
+
|
41 |
+
1. De-activate the plugin
|
42 |
+
1. Get and upload the new files (do steps 1. - 3. from "new installation" instructions)
|
43 |
+
1. Reactivate the plugin. Your settings should have been retained from the previous version.
|
44 |
+
|
45 |
+
== Changelog ==
|
46 |
+
|
47 |
+
= 0.1.5 =
|
48 |
+
* First release on wordpress.org
|
49 |
+
* Moved all images into a separate directory.
|
50 |
+
* Added a readme.txt
|
shadow_plugin_framework.php
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* @author W-Shadow
|
5 |
+
* @copyright 2008
|
6 |
+
*/
|
7 |
+
|
8 |
+
//Make sure the needed constants are defined
|
9 |
+
if ( ! defined( 'WP_CONTENT_URL' ) )
|
10 |
+
define( 'WP_CONTENT_URL', get_option( 'siteurl' ) . '/wp-content' );
|
11 |
+
if ( ! defined( 'WP_CONTENT_DIR' ) )
|
12 |
+
define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
|
13 |
+
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.1.2';
|
20 |
+
|
21 |
+
protected $options = array();
|
22 |
+
public $option_name = ''; //should be set or overriden by the plugin
|
23 |
+
protected $defaults = array(); //should be set or overriden by the plugin
|
24 |
+
|
25 |
+
public $plugin_file = ''; //Filename of the plugin.
|
26 |
+
public $plugin_basename = ''; //Basename of the plugin, as returned by plugin_basename().
|
27 |
+
public $plugin_dir_url = ''; //The URL of the plugin's folder
|
28 |
+
|
29 |
+
protected $magic_hooks = false; //Automagically set up hooks for all methods named "hook_[hookname]" .
|
30 |
+
protected $magic_hook_priority = 10; //Priority for magically set hooks.
|
31 |
+
|
32 |
+
protected $settings_link = ''; //If set, this will be automatically added after "Deactivate"/"Edit".
|
33 |
+
|
34 |
+
/**
|
35 |
+
* ShadowPluginFramework::__construct()
|
36 |
+
* Initializes the plugin and loads settings from the database.
|
37 |
+
*
|
38 |
+
* @param string $plugin_file Plugin's filename. Usuallly you can just use __FILE__.
|
39 |
+
* @return void
|
40 |
+
*/
|
41 |
+
protected function __construct( $plugin_file = ''){
|
42 |
+
if ($plugin_file == ''){
|
43 |
+
//Try to guess the name of the file that included this file.
|
44 |
+
//XXXXXX - not implemented yet.
|
45 |
+
}
|
46 |
+
$this->plugin_file = $plugin_file;
|
47 |
+
$this->plugin_basename = plugin_basename($this->plugin_file);
|
48 |
+
$this->plugin_dir_url = WP_PLUGIN_URL . '/' . dirname($this->plugin_basename);
|
49 |
+
|
50 |
+
/************************************
|
51 |
+
Load settings
|
52 |
+
************************************/
|
53 |
+
//The provided $option_name overrides the default only if it is set to something useful
|
54 |
+
if ( $this->option_name == '' ) {
|
55 |
+
//Generate a unique name
|
56 |
+
$this->option_name = 'plugin_'.md5($this->plugin_basename);
|
57 |
+
}
|
58 |
+
|
59 |
+
//Do we need to load the plugin's settings?
|
60 |
+
if ($this->option_name != null){
|
61 |
+
$this->load_options();
|
62 |
+
}
|
63 |
+
|
64 |
+
/************************************
|
65 |
+
Add the default hooks
|
66 |
+
************************************/
|
67 |
+
add_action('activate_'.$this->plugin_basename, array(&$this,'activate'));
|
68 |
+
add_action('deactivate_'.$this->plugin_basename, array(&$this,'deactivate'));
|
69 |
+
|
70 |
+
if ($this->settings_link)
|
71 |
+
add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
|
72 |
+
|
73 |
+
if ($this->magic_hooks)
|
74 |
+
$this->set_magic_hooks();
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* ShadowPluginFramework::load_options()
|
79 |
+
* Loads the plugin's configuration : loads an option specified by $this->option_name into $this->options.
|
80 |
+
*
|
81 |
+
* @return boolean TRUE if options were loaded okay and FALSE otherwise.
|
82 |
+
*/
|
83 |
+
function load_options(){
|
84 |
+
$this->options = get_option($this->option_name);
|
85 |
+
if(!is_array($this->options)){
|
86 |
+
$this->options = $this->defaults;
|
87 |
+
return false;
|
88 |
+
} else {
|
89 |
+
$this->options = array_merge($this->defaults, $this->options);
|
90 |
+
return true;
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* ShadowPluginFramework::set_magic_hooks()
|
96 |
+
* Automagically sets up hooks for all methods named "hook_[tag]". Uses the Reflection API.
|
97 |
+
*
|
98 |
+
* @return void
|
99 |
+
*/
|
100 |
+
function set_magic_hooks(){
|
101 |
+
$class = new ReflectionClass(get_class($this));
|
102 |
+
$methods = $class->getMethods();
|
103 |
+
|
104 |
+
foreach ($methods as $method){
|
105 |
+
//Check if the method name starts with "hook_"
|
106 |
+
if (strpos($method->name, 'hook_') === 0){
|
107 |
+
//Get the hook's tag from the method name
|
108 |
+
$hook = substr($method->name, 5);
|
109 |
+
//Add the hook. Uses add_filter because add_action is simply a wrapper of the same.
|
110 |
+
add_filter($hook, array(&$this, $method->name),
|
111 |
+
$this->magic_hook_priority, $method->getNumberOfParameters());
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
unset($class);
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* ShadowPluginFramework::save_options()
|
120 |
+
* Saves the $options array to the database.
|
121 |
+
*
|
122 |
+
* @return void
|
123 |
+
*/
|
124 |
+
function save_options(){
|
125 |
+
if ($this->option_name)
|
126 |
+
update_option($this->option_name, $this->options);
|
127 |
+
}
|
128 |
+
|
129 |
+
/**
|
130 |
+
* ShadowPluginFramework::activate()
|
131 |
+
* Stub function for the activation hook. Simply stores the default configuration.
|
132 |
+
*
|
133 |
+
* @return void
|
134 |
+
*/
|
135 |
+
function activate(){
|
136 |
+
$this->save_options();
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* ShadowPluginFramework::deactivate()
|
141 |
+
* Stub function for the deactivation hook. Does nothing.
|
142 |
+
*
|
143 |
+
* @return void
|
144 |
+
*/
|
145 |
+
function deactivate(){
|
146 |
+
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* ShadowPluginFramework::plugin_action_links()
|
151 |
+
* Adds a "Settings" link to the plugin's action links. Default handler for the 'plugin_action_links' hook.
|
152 |
+
*
|
153 |
+
* @param array $links
|
154 |
+
* @param string $file
|
155 |
+
* @return array
|
156 |
+
*/
|
157 |
+
function plugin_action_links($links, $file) {
|
158 |
+
if ($file == $this->plugin_basename)
|
159 |
+
$links[] = "<a href='" . $this->settings_link . "'>" . __('Settings') . "</a>";
|
160 |
+
return $links;
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* ShadowPluginFramework::uninstall()
|
165 |
+
* Default uninstaller. Removes the plugins configuration record (if available).
|
166 |
+
*
|
167 |
+
* @return void
|
168 |
+
*/
|
169 |
+
function uninstall(){
|
170 |
+
if ($this->option_name)
|
171 |
+
delete_option($this->option_name);
|
172 |
+
}
|
173 |
+
|
174 |
+
}
|
175 |
+
|
176 |
+
?>
|
uninstall.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* @author W-Shadow
|
5 |
+
* @copyright 2009
|
6 |
+
*
|
7 |
+
* The uninstallation script.
|
8 |
+
*/
|
9 |
+
|
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 |
+
?>
|