Admin Menu Editor - Version 1.8

Version Description

  • You can edit plugin names and descriptions through the "Plugins" tab. This only changes how plugins are displayed on the "Plugins" page. It doesn't affect plugin files on disk.
  • Added an option to highlight new menu items. This feature is off by default. You can enable it in the "Settings" tab.
  • Added an option to compress menu data that the plugin stores in the database.
  • Added a compatibility workaround for the Divi Training plugin. The hidden menu items that it adds to the "Dashboard" menu should no longer show up when you activate AME.
  • Added a workaround that improves compatibility with plugins that set their menu icons using CSS.
  • Fixed an old bug where sorting menu items would put all separators at the top. Now they'll stay near their preceding menu item.
  • Fixed incorrect shadows on custom screen options links.
  • Fixed a couple of UI layout issues that were caused by bugs in other plugins.
  • Fixed a rare issue where hiding the admin bar would leave behind empty space.
  • When you use the "A-Z" button to sort top level menus, it also sorts submenu items. To avoid compatibility issues, the first item of each submenu stays in its original position.
  • Automatically reset plugin access if the only allowed user no longer exists. This should cut down on the number of users who accidentally lock themselves out by setting "Who can access the plugin" to "Only the current user" and then later deleting that user account.
  • Minor performance optimizations.
Download this release

Release Info

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

Code changes from version 1.7.3 to 1.8

Files changed (45) hide show
  1. ajax-wrapper/AjaxWrapper.php +464 -0
  2. ajax-wrapper/README.md +23 -0
  3. ajax-wrapper/ajax-action-wrapper.d.ts +15 -0
  4. ajax-wrapper/ajax-action-wrapper.js +139 -0
  5. css/_dashicons.scss +224 -0
  6. css/force-dashicons.css +460 -0
  7. css/force-dashicons.css.map +7 -0
  8. css/force-dashicons.scss +20 -0
  9. css/menu-editor.css +19 -6
  10. css/menu-editor.scss +24 -6
  11. css/screen-meta.css +5 -3
  12. css/style-modern-one.css +27 -27
  13. css/style-modern-one.scss +39 -32
  14. includes/ajax-helper.php +0 -178
  15. includes/auto-versioning.php +11 -8
  16. includes/editor-page.php +21 -1
  17. includes/generate-menu-dashicons.php +21 -3
  18. includes/menu-editor-core.php +346 -83
  19. includes/menu-item.php +10 -0
  20. includes/menu.php +60 -6
  21. includes/reflection-callable.php +16 -2
  22. includes/settings-page.php +105 -0
  23. js/actor-manager.js +38 -28
  24. js/actor-manager.ts +21 -19
  25. js/menu-editor.js +23 -6
  26. menu-editor.php +1 -1
  27. modules/actor-selector/actor-selector.js +1 -1
  28. modules/actor-selector/actor-selector.php +13 -8
  29. modules/actor-selector/actor-selector.ts +9 -9
  30. modules/admin-css/admin-css.php +35 -0
  31. modules/highlight-new-menus/assets/highlight-menus.js +135 -0
  32. modules/highlight-new-menus/assets/menu-highlights.css +12 -0
  33. modules/highlight-new-menus/assets/menu-highlights.css.map +7 -0
  34. modules/highlight-new-menus/assets/menu-highlights.scss +23 -0
  35. modules/highlight-new-menus/highlight-new-menus.php +12 -0
  36. modules/highlight-new-menus/plugin.php +15 -0
  37. modules/highlight-new-menus/uninstall.php +5 -0
  38. modules/highlight-new-menus/wsNewMenuHighlighter.php +266 -0
  39. modules/plugin-visibility/plugin-visibility-template.php +61 -1
  40. modules/plugin-visibility/plugin-visibility.css +19 -0
  41. modules/plugin-visibility/plugin-visibility.js +63 -11
  42. modules/plugin-visibility/plugin-visibility.php +27 -9
  43. modules/plugin-visibility/plugin-visibility.scss +34 -1
  44. modules/plugin-visibility/plugin-visibility.ts +97 -27
  45. readme.txt +16 -2
ajax-wrapper/AjaxWrapper.php ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!class_exists('Ajaw_v1_ActionBuilder', false)):
3
+
4
+ class Ajaw_v1_ActionBuilder {
5
+ private $action;
6
+ private $callback = '__return_null';
7
+ private $params = array();
8
+ private $httpMethod = null;
9
+
10
+ private $capability = null;
11
+ private $permissionCheckCallback = null;
12
+
13
+ private $mustBeLoggedIn = true;
14
+ private $checkNonce = true;
15
+
16
+ public function __construct($action) {
17
+ $this->action = $action;
18
+ }
19
+
20
+ /**
21
+ * @param callable $callback
22
+ * @return $this
23
+ */
24
+ public function handler($callback) {
25
+ $this->callback = $callback;
26
+ return $this;
27
+ }
28
+
29
+ public function requiredParam($name, $type = null, $validateCallback = null) {
30
+ return $this->addParameter($name, $type, true, null, $validateCallback);
31
+ }
32
+
33
+ public function optionalParam($name, $defaultValue = null, $type = null, $validateCallback = null) {
34
+ return $this->addParameter($name, $type, false, $defaultValue, $validateCallback);
35
+ }
36
+
37
+ private function addParameter($name, $type, $required, $defaultValue, $validateCallback) {
38
+ if (isset($type) && !isset(Ajaw_v1_Action::$defaultValidators[$type])) {
39
+ throw new LogicException(sprintf(
40
+ 'Unknown parameter type "%s". Supported types are: %s.',
41
+ $type,
42
+ implode(', ', array_keys(Ajaw_v1_Action::$defaultValidators[$type]))
43
+ ));
44
+ }
45
+
46
+ $this->params[$name] = array(
47
+ 'required' => $required,
48
+ 'defaultValue' => $defaultValue,
49
+ 'type' => $type,
50
+ 'validateCallback' => $validateCallback,
51
+ );
52
+ return $this;
53
+ }
54
+
55
+ public function method($httpMethod) {
56
+ $this->httpMethod = strtoupper($httpMethod);
57
+ return $this;
58
+ }
59
+
60
+ public function requiredCap($capability) {
61
+ $this->capability = $capability;
62
+ return $this;
63
+ }
64
+
65
+ public function permissionCallback($callback) {
66
+ $this->permissionCheckCallback = $callback;
67
+ return $this;
68
+ }
69
+
70
+ public function allowUnprivilegedUsers() {
71
+ $this->mustBeLoggedIn = false;
72
+ return $this;
73
+ }
74
+
75
+ public function withoutNonce() {
76
+ $this->checkNonce = false;
77
+ return $this;
78
+ }
79
+
80
+ public function build() {
81
+ $instance = new Ajaw_v1_Action($this->action, $this->callback, $this->params);
82
+
83
+ $instance->mustBeLoggedIn = $this->mustBeLoggedIn;
84
+ $instance->requiredCap = $this->capability;
85
+ $instance->nonceCheckEnabled = $this->checkNonce;
86
+ $instance->method = $this->httpMethod;
87
+ $instance->permissionCallback = $this->permissionCheckCallback;
88
+
89
+ return $instance;
90
+ }
91
+
92
+ public function register() {
93
+ $instance = $this->build();
94
+ $instance->register();
95
+ return $instance;
96
+ }
97
+ }
98
+
99
+ endif;
100
+
101
+ if (!class_exists('Ajaw_v1_Action', false)):
102
+
103
+ class Ajaw_v1_Action {
104
+ public $action;
105
+ public $callback;
106
+ public $params = array();
107
+ public $method = null;
108
+
109
+ public $requiredCap = null;
110
+ public $mustBeLoggedIn = false;
111
+ public $nonceCheckEnabled = true;
112
+ public $permissionCallback = null;
113
+
114
+ private $isScriptRegistered = false;
115
+
116
+ public $get = array();
117
+ public $post = array();
118
+ public $request = array();
119
+
120
+ public static $defaultValidators = array(
121
+ 'int' => array(__CLASS__, 'validateInt'),
122
+ 'float' => array(__CLASS__, 'validateFloat'),
123
+ 'boolean' => array(__CLASS__, 'validateBoolean'),
124
+ 'string' => array(__CLASS__, 'validateString'),
125
+ );
126
+
127
+ public function __construct($action, $callback, $params) {
128
+ $this->action = $action;
129
+ $this->callback = $callback;
130
+ $this->params = $params;
131
+
132
+ if (empty($this->action)) {
133
+ throw new LogicException(sprintf(
134
+ 'AJAX action name is missing. You must either pass it to the %1$s constructor '
135
+ . 'or give the %1$s::$action property a valid default value.',
136
+ get_class($this)
137
+ ));
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Set up hooks for AJAX and helper scripts.
143
+ */
144
+ public function register() {
145
+ //Register the AJAX handler(s).
146
+ $hookNames = array('wp_ajax_' . $this->action);
147
+ if (!$this->mustBeLoggedIn) {
148
+ $hookNames[] = 'wp_ajax_nopriv_' . $this->action;
149
+ }
150
+
151
+ foreach($hookNames as $hook) {
152
+ if (has_action($hook)) {
153
+ throw new RuntimeException(sprintf('The action name "%s" is already in use.', $this->action));
154
+ }
155
+ add_action($hook, array($this, 'processAjaxRequest'));
156
+ }
157
+
158
+ //Register the utility JS library after WP is fully loaded.
159
+ if (did_action('wp_loaded')) {
160
+ $this->registerScript();
161
+ } else {
162
+ add_action('wp_loaded', array($this, 'registerScript'), 2);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * @access protected
168
+ */
169
+ public function processAjaxRequest() {
170
+ $result = $this->handleAction();
171
+
172
+ if (is_wp_error($result)) {
173
+ $statusCode = $result->get_error_data();
174
+ if (isset($statusCode) && is_int($statusCode) ) {
175
+ status_header($statusCode);
176
+ }
177
+
178
+ $errorResponse = array(
179
+ 'error' => array(
180
+ 'message' => $result->get_error_message(),
181
+ 'code' => $result->get_error_code()
182
+ )
183
+ );
184
+
185
+ $result = $errorResponse;
186
+ }
187
+
188
+ if (isset($result)) {
189
+ $this->outputJSON($result);
190
+ }
191
+ exit;
192
+ }
193
+
194
+ protected function handleAction() {
195
+ $method = strtoupper(filter_input(INPUT_SERVER, 'REQUEST_METHOD'));
196
+ if (isset($this->method) && ($method !== $this->method)) {
197
+ return new WP_Error(
198
+ 'http_method_not_allowed',
199
+ 'The HTTP method is not supported by the request handler.',
200
+ 405
201
+ );
202
+ }
203
+
204
+ $isAuthorized = $this->checkAuthorization();
205
+ if ($isAuthorized !== true) {
206
+ return $isAuthorized;
207
+ }
208
+
209
+ $params = $this->parseParameters();
210
+ if ($params instanceof WP_Error) {
211
+ return $params;
212
+ }
213
+
214
+ //Call the user-specified action handler.
215
+ if (is_callable($this->callback)) {
216
+ return call_user_func($this->callback, $params);
217
+ } else {
218
+ return new WP_Error(
219
+ 'missing_ajax_handler',
220
+ sprintf(
221
+ 'There is no request handler assigned to the "%1$s" action. '
222
+ . 'Either pass a valid callback to $builder->request() or override the %2$s::%3$s method.',
223
+ $this->action,
224
+ __CLASS__,
225
+ __METHOD__
226
+ ),
227
+ 500
228
+ );
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Check if the current user is authorized to perform this action.
234
+ *
235
+ * @return bool|WP_Error
236
+ */
237
+ protected function checkAuthorization() {
238
+ if ($this->mustBeLoggedIn && !is_user_logged_in()) {
239
+ return new WP_Error('login_required', 'You must be logged in to perform this action.', 403);
240
+ }
241
+
242
+ if (isset($this->requiredCap) && !current_user_can($this->requiredCap)) {
243
+ return new WP_Error('capability_missing', 'You don\'t have permission to perform this action.', 403);
244
+ }
245
+
246
+ if ($this->nonceCheckEnabled && !check_ajax_referer($this->action, false, false)) {
247
+ return new WP_Error('nonce_check_failed', 'Invalid or missing nonce.', 403);
248
+ }
249
+
250
+ if (isset($this->permissionCallback)) {
251
+ $result = call_user_func($this->permissionCallback);
252
+ if ($result === false) {
253
+ return new WP_Error(
254
+ 'permission_callback_failed',
255
+ 'You don\'t have permission to perform this action.',
256
+ 403
257
+ );
258
+ } else if (is_wp_error($result)) {
259
+ return $result;
260
+ }
261
+ }
262
+
263
+ return true;
264
+ }
265
+
266
+ protected function parseParameters() {
267
+ $method = strtoupper(filter_input(INPUT_SERVER, 'REQUEST_METHOD'));
268
+
269
+ //Retrieve request parameters.
270
+ if ($method === 'GET') {
271
+ $rawParams = $_GET;
272
+ } else if ($method === 'POST') {
273
+ $rawParams = $_POST;
274
+ } else {
275
+ $rawParams = $_REQUEST;
276
+ }
277
+
278
+ //Remove magic quotes. WordPress applies them in wp-settings.php.
279
+ //There's no hook for wp_magic_quotes, so we use one that's closest in execution order.
280
+ if (did_action('sanitize_comment_cookies') && function_exists('wp_magic_quotes')) {
281
+ $rawParams = wp_unslash($rawParams);
282
+ }
283
+
284
+ //Validate all parameters.
285
+ $inputParams = $rawParams;
286
+ foreach($this->params as $name => $settings) {
287
+ //Verify that all of the required parameters are present.
288
+ //Empty strings are treated as missing parameters.
289
+ if (isset($inputParams[$name]) && ($inputParams[$name] !== '')) {
290
+ $value = $this->validateParameter($settings, $inputParams[$name], $name);
291
+ if (is_wp_error($value)) {
292
+ return $value;
293
+ } else {
294
+ $inputParams[$name] = $value;
295
+ }
296
+ } else if (empty($settings['required'])) {
297
+ //It's an optional parameter. Use the default value.
298
+ $inputParams[$name] = $settings['defaultValue'];
299
+ } else {
300
+ return new WP_Error(
301
+ 'missing_required_parameter',
302
+ sprintf('Required parameter is missing or empty: "%s".', $name),
303
+ 400
304
+ );
305
+ }
306
+ }
307
+
308
+ return $inputParams;
309
+ }
310
+
311
+ protected function validateParameter($settings, $value, $name) {
312
+ if (isset($settings['type'])) {
313
+ $value = call_user_func(self::$defaultValidators[$settings['type']], $value, $name);
314
+ if (is_wp_error($value)) {
315
+ return $value;
316
+ }
317
+ }
318
+ if (isset($settings['validateCallback'])) {
319
+ $success = call_user_func($settings['validateCallback'], $value);
320
+ if (is_wp_error($success)) {
321
+ return $success;
322
+ } else if ($success === false) {
323
+ return new WP_Error(
324
+ 'invalid_parameter_value',
325
+ sprintf('The value of the parameter "%s" is invalid.', $name),
326
+ 400
327
+ );
328
+ }
329
+ }
330
+ return $value;
331
+ }
332
+
333
+ private static function validateInt($value, $name) {
334
+ $result = filter_var($value, FILTER_VALIDATE_INT);
335
+ if ($result === false) {
336
+ return new WP_Error(
337
+ 'invalid_parameter_value',
338
+ sprintf('The value of the parameter "%s" is invalid. It must be an integer.', $name),
339
+ 400
340
+ );
341
+ }
342
+ return $result;
343
+ }
344
+
345
+ private static function validateFloat($value, $name) {
346
+ $result = filter_var($value, FILTER_VALIDATE_FLOAT);
347
+ if ($result === false) {
348
+ return new WP_Error(
349
+ 'invalid_parameter_value',
350
+ sprintf('The value of the parameter "%s" is invalid. It must be a float.', $name),
351
+ 400
352
+ );
353
+ }
354
+ return $result;
355
+ }
356
+
357
+ private static function validateBoolean($value, $name) {
358
+ $result = filter_var($value, FILTER_VALIDATE_BOOLEAN, array('flags' => FILTER_NULL_ON_FAILURE));
359
+ if ($result === null) {
360
+ return new WP_Error(
361
+ 'invalid_parameter_value',
362
+ sprintf('The value of the parameter "%s" is invalid. It must be a boolean.', $name),
363
+ 400
364
+ );
365
+ }
366
+ return $result;
367
+ }
368
+
369
+ private static function validateString($value, $name) {
370
+ if (!is_string($value)) {
371
+ return new WP_Error(
372
+ 'invalid_parameter_value',
373
+ sprintf('The value of the parameter "%s" is invalid. It must be a string.', $name),
374
+ 400
375
+ );
376
+ }
377
+ return $value;
378
+ }
379
+
380
+ protected function outputJSON($response) {
381
+ @header('Content-Type: application/json; charset=' . get_option('blog_charset'));
382
+ echo json_encode($response);
383
+ }
384
+
385
+ public function registerScript() {
386
+ if ($this->isScriptRegistered) {
387
+ return;
388
+ }
389
+ $this->isScriptRegistered = true;
390
+
391
+ //There could be multiple instances of this class, but we only need to register the script once.
392
+ $handle = $this->getScriptHandle();
393
+ if (!wp_script_is($handle, 'registered')) {
394
+ wp_register_script(
395
+ $handle,
396
+ plugins_url('ajax-action-wrapper.js', __FILE__),
397
+ array('jquery'),
398
+ '20161105'
399
+ );
400
+ }
401
+
402
+ //Pass the action to the script.
403
+ if (function_exists('wp_add_inline_script')) {
404
+ wp_add_inline_script($handle, $this->generateActionJs(), 'after'); //WP 4.5+
405
+ } else {
406
+ add_filter('script_loader_tag', array($this, 'addRegistrationScript'), 10, 2); //WP 4.1+
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Backwards compatibility for older versions of WP that don't have wp_add_inline_script().
412
+ * @internal
413
+ *
414
+ * @param string $tag
415
+ * @param string $handle
416
+ * @return string
417
+ */
418
+ public function addRegistrationScript($tag, $handle) {
419
+ if ($handle === $this->getScriptHandle()) {
420
+ $tag .= '<script type="text/javascript">' . $this->generateActionJs() . '</script>';
421
+ }
422
+ return $tag;
423
+ }
424
+
425
+ protected function generateActionJs() {
426
+ $properties = array(
427
+ 'ajaxUrl' => admin_url('admin-ajax.php'),
428
+ 'method' => $this->method,
429
+ 'nonce' => $this->nonceCheckEnabled ? wp_create_nonce($this->action) : null,
430
+ );
431
+
432
+ return sprintf(
433
+ 'AjawV1.actionRegistry.add("%s", %s);' . "\n",
434
+ esc_js($this->action),
435
+ json_encode($properties)
436
+ );
437
+ }
438
+
439
+ public function getScriptHandle() {
440
+ return 'ajaw-v1-ajax-action-wrapper';
441
+ }
442
+
443
+ /**
444
+ * Capture $_GET, $_POST and $_REQUEST without magic quotes.
445
+ */
446
+ function captureRequestVars() {
447
+ $this->post = $_POST;
448
+ $this->get = $_GET;
449
+ $this->request = $_REQUEST;
450
+
451
+ if ( function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() ) {
452
+ $this->post = stripslashes_deep($this->post);
453
+ $this->get = stripslashes_deep($this->get);
454
+ }
455
+ }
456
+ }
457
+
458
+ endif;
459
+
460
+ if (!function_exists('ajaw_v1_CreateAction')) {
461
+ function ajaw_v1_CreateAction($action) {
462
+ return new Ajaw_v1_ActionBuilder($action);
463
+ }
464
+ }
ajax-wrapper/README.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AJAX Action Wrapper
2
+
3
+ **Warning: Work in progress.** Not intended for public consumption. There is no documentation.
4
+
5
+ This helper library makes it easier to handle AJAX requests in WordPress plugins.
6
+
7
+ ### Goals
8
+ - Automate common, boring stuff.
9
+ - [x] Automatically pass the `admin-ajax.php` URL and nonce to JS.
10
+ - [x] Define required parameters.
11
+ - [x] Define optional parameters with default values.
12
+ - [x] Automatically remove "magic quotes" that WordPress adds to `$_GET`, `$_POST` and `$_REQUEST`.
13
+ - [x] Encode return values as JSON.
14
+ - Security should be the default.
15
+ - [x] Generate and verify nonces. Nonce verification is on by default, but can be disabled.
16
+ - [x] Check capabilities.
17
+ - [x] Verify that all required parameters are set.
18
+ - [x] Validate parameter values.
19
+ - [x] Set the required HTTP method.
20
+ - Resilience.
21
+ - [ ] Lenient response parsing to work around bugs in other plugins. For example, deal with extraneous whitespace and PHP notices in AJAX responses.
22
+ - [ ] Multiple versions of the library can coexist on the same site.
23
+
ajax-wrapper/ajax-action-wrapper.d.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Basic type definitions for the Ajaw AJAX wrapper library 1.0
2
+
3
+ declare namespace AjawV1 {
4
+ interface RequestParams { [name: string]: any }
5
+ interface SuccessCallback { (data, textStatus: string, jqXHR): string }
6
+ interface ErrorCallback { (data, textStatus: string, jqXHR, errorThrown): string }
7
+
8
+ class AjawAjaxAction {
9
+ get(params?: RequestParams, success?: SuccessCallback, error?: ErrorCallback): void;
10
+ post(params?: RequestParams, success?: SuccessCallback, error?: ErrorCallback): void;
11
+ request(params?: RequestParams, success?: SuccessCallback, error?: ErrorCallback, method?: string): void;
12
+ }
13
+
14
+ function getAction(action: string): AjawAjaxAction;
15
+ }
ajax-wrapper/ajax-action-wrapper.js ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var AjawV1 = window.AjawV1 || {};
2
+
3
+ AjawV1.AjaxAction = (function () {
4
+ "use strict";
5
+
6
+ function AjawAjaxAction(action, properties) {
7
+ this.action = action;
8
+ this.ajaxUrl = properties['ajaxUrl'];
9
+ this.nonce = properties['nonce'];
10
+ this.requiredMethod = (typeof properties['method'] !== 'undefined') ? properties['method'] : null;
11
+ }
12
+
13
+ /**
14
+ * Send a POST request.
15
+ *
16
+ * @param {Object} params
17
+ * @param {Function} success
18
+ * @param {Function} [error]
19
+ */
20
+ AjawAjaxAction.prototype.post = function (params, success, error) {
21
+ return this.request(params, success, error, 'POST');
22
+ };
23
+
24
+ /**
25
+ * Send a GET request.
26
+ *
27
+ * @param {Object} params
28
+ * @param {Function} success
29
+ * @param {Function} [error]
30
+ */
31
+ AjawAjaxAction.prototype.get = function(params, success, error) {
32
+ return this.request(params, success, error, 'GET');
33
+ };
34
+
35
+ /**
36
+ * Send an AJAX request using the specified HTTP method.
37
+ *
38
+ * @param {Object} params
39
+ * @param {Function} success
40
+ * @param {Function} [error]
41
+ * @param {String} [method]
42
+ */
43
+ AjawAjaxAction.prototype.request = function(params, success, error, method) {
44
+ if (typeof params === 'function') {
45
+ //It looks like "params" was omitted and the first argument is actually the success callback.
46
+ //Shift all arguments left one step. The reverse order is due to argument binding shenanigans.
47
+ method = arguments[2];
48
+ error = arguments[1];
49
+ success = arguments[0];
50
+ params = {};
51
+ }
52
+
53
+ if (typeof params === 'undefined') {
54
+ params = {};
55
+ } else if (typeof params !== 'object') {
56
+ //While jQuery accepts request data in object and string form, this library only supports objects.
57
+ throw 'Data that\'s to be sent to the server must be an object, not ' + (typeof params);
58
+ }
59
+
60
+ if (typeof method === 'undefined') {
61
+ method = this.requiredMethod || 'POST';
62
+ }
63
+ if (this.requiredMethod && (method !== this.requiredMethod)) {
64
+ throw 'Wrong HTTP method. This action requires ' + this.requiredMethod;
65
+ }
66
+
67
+ //noinspection JSUnusedGlobalSymbols
68
+ return jQuery.ajax(
69
+ this.ajaxUrl,
70
+ {
71
+ method: method,
72
+ data: this.prepareRequestParams(params),
73
+ success: function(data, textStatus, jqXHR) {
74
+ if (success) {
75
+ success(data, textStatus, jqXHR);
76
+ }
77
+ },
78
+ error: function(jqXHR, textStatus, errorThrown) {
79
+ var data = jqXHR.responseText;
80
+ if (typeof jqXHR['responseJSON'] !== 'undefined') {
81
+ data = jqXHR['responseJSON'];
82
+ } else if (typeof jqXHR['responseXML'] !== 'undefined') {
83
+ data = jqXHR['responseXML'];
84
+ }
85
+
86
+ if (error) {
87
+ error(data, textStatus, jqXHR, errorThrown);
88
+ }
89
+ }
90
+ }
91
+ );
92
+ };
93
+
94
+ AjawAjaxAction.prototype.prepareRequestParams = function(params) {
95
+ if (params === null) {
96
+ params = {};
97
+ }
98
+
99
+ params['action'] = this.action;
100
+ if (this.nonce !== null) {
101
+ params['_ajax_nonce'] = this.nonce;
102
+ }
103
+ return params;
104
+ };
105
+
106
+ return AjawAjaxAction;
107
+ }());
108
+
109
+ AjawV1.actionRegistry = (function() {
110
+ var actions = {};
111
+
112
+ return {
113
+ /**
114
+ *
115
+ * @param {String} actionName
116
+ * @return {AjawAjaxAction}
117
+ */
118
+ get: function(actionName) {
119
+ if (actions.hasOwnProperty(actionName)) {
120
+ return actions[actionName];
121
+ }
122
+ return null;
123
+ },
124
+
125
+ add: function(actionName, properties) {
126
+ actions[actionName] = new AjawV1.AjaxAction(actionName, properties);
127
+ }
128
+ }
129
+ })();
130
+
131
+ /**
132
+ * Get a registered action wrapper.
133
+ *
134
+ * @param {string} action
135
+ * @return {AjawAjaxAction|null}
136
+ */
137
+ AjawV1.getAction = function(action) {
138
+ return this.actionRegistry.get(action);
139
+ };
css/_dashicons.scss ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ This file was automatically generated from /wp-includes/css/dashicons.css.
3
+ Last update: 2017-06-07T16:55:55+00:00
4
+ */
5
+ .dashicons-menu:before { content: "\f333" !important; }
6
+ .dashicons-admin-site:before { content: "\f319" !important; }
7
+ .dashicons-admin-media:before { content: "\f104" !important; }
8
+ .dashicons-admin-page:before { content: "\f105" !important; }
9
+ .dashicons-admin-comments:before { content: "\f101" !important; }
10
+ .dashicons-admin-appearance:before { content: "\f100" !important; }
11
+ .dashicons-admin-plugins:before { content: "\f106" !important; }
12
+ .dashicons-admin-users:before { content: "\f110" !important; }
13
+ .dashicons-admin-tools:before { content: "\f107" !important; }
14
+ .dashicons-admin-settings:before { content: "\f108" !important; }
15
+ .dashicons-admin-network:before { content: "\f112" !important; }
16
+ .dashicons-admin-generic:before { content: "\f111" !important; }
17
+ .dashicons-admin-home:before { content: "\f102" !important; }
18
+ .dashicons-admin-collapse:before { content: "\f148" !important; }
19
+ .dashicons-filter:before { content: "\f536" !important; }
20
+ .dashicons-admin-customizer:before { content: "\f540" !important; }
21
+ .dashicons-admin-multisite:before { content: "\f541" !important; }
22
+ .dashicons-admin-links:before, .dashicons-format-links:before { content: "\f103" !important; }
23
+ .dashicons-admin-post:before, .dashicons-format-standard:before { content: "\f109" !important; }
24
+ .dashicons-format-image:before { content: "\f128" !important; }
25
+ .dashicons-format-gallery:before { content: "\f161" !important; }
26
+ .dashicons-format-audio:before { content: "\f127" !important; }
27
+ .dashicons-format-video:before { content: "\f126" !important; }
28
+ .dashicons-format-chat:before { content: "\f125" !important; }
29
+ .dashicons-format-status:before { content: "\f130" !important; }
30
+ .dashicons-format-aside:before { content: "\f123" !important; }
31
+ .dashicons-format-quote:before { content: "\f122" !important; }
32
+ .dashicons-welcome-write-blog:before, .dashicons-welcome-edit-page:before { content: "\f119" !important; }
33
+ .dashicons-welcome-add-page:before { content: "\f133" !important; }
34
+ .dashicons-welcome-view-site:before { content: "\f115" !important; }
35
+ .dashicons-welcome-widgets-menus:before { content: "\f116" !important; }
36
+ .dashicons-welcome-comments:before { content: "\f117" !important; }
37
+ .dashicons-welcome-learn-more:before { content: "\f118" !important; }
38
+ .dashicons-image-crop:before { content: "\f165" !important; }
39
+ .dashicons-image-rotate:before { content: "\f531" !important; }
40
+ .dashicons-image-rotate-left:before { content: "\f166" !important; }
41
+ .dashicons-image-rotate-right:before { content: "\f167" !important; }
42
+ .dashicons-image-flip-vertical:before { content: "\f168" !important; }
43
+ .dashicons-image-flip-horizontal:before { content: "\f169" !important; }
44
+ .dashicons-image-filter:before { content: "\f533" !important; }
45
+ .dashicons-undo:before { content: "\f171" !important; }
46
+ .dashicons-redo:before { content: "\f172" !important; }
47
+ .dashicons-editor-ul:before { content: "\f203" !important; }
48
+ .dashicons-editor-ol:before { content: "\f204" !important; }
49
+ .dashicons-editor-quote:before { content: "\f205" !important; }
50
+ .dashicons-editor-alignleft:before { content: "\f206" !important; }
51
+ .dashicons-editor-aligncenter:before { content: "\f207" !important; }
52
+ .dashicons-editor-alignright:before { content: "\f208" !important; }
53
+ .dashicons-editor-insertmore:before { content: "\f209" !important; }
54
+ .dashicons-editor-spellcheck:before { content: "\f210" !important; }
55
+ .dashicons-editor-distractionfree:before, .dashicons-editor-expand:before { content: "\f211" !important; }
56
+ .dashicons-editor-contract:before { content: "\f506" !important; }
57
+ .dashicons-editor-kitchensink:before { content: "\f212" !important; }
58
+ .dashicons-editor-underline:before { content: "\f213" !important; }
59
+ .dashicons-editor-justify:before { content: "\f214" !important; }
60
+ .dashicons-editor-textcolor:before { content: "\f215" !important; }
61
+ .dashicons-editor-paste-word:before { content: "\f216" !important; }
62
+ .dashicons-editor-paste-text:before { content: "\f217" !important; }
63
+ .dashicons-editor-removeformatting:before { content: "\f218" !important; }
64
+ .dashicons-editor-video:before { content: "\f219" !important; }
65
+ .dashicons-editor-customchar:before { content: "\f220" !important; }
66
+ .dashicons-editor-outdent:before { content: "\f221" !important; }
67
+ .dashicons-editor-indent:before { content: "\f222" !important; }
68
+ .dashicons-editor-help:before { content: "\f223" !important; }
69
+ .dashicons-editor-strikethrough:before { content: "\f224" !important; }
70
+ .dashicons-editor-unlink:before { content: "\f225" !important; }
71
+ .dashicons-editor-rtl:before { content: "\f320" !important; }
72
+ .dashicons-editor-break:before { content: "\f474" !important; }
73
+ .dashicons-editor-code:before { content: "\f475" !important; }
74
+ .dashicons-editor-paragraph:before { content: "\f476" !important; }
75
+ .dashicons-editor-table:before { content: "\f535" !important; }
76
+ .dashicons-align-left:before { content: "\f135" !important; }
77
+ .dashicons-align-right:before { content: "\f136" !important; }
78
+ .dashicons-align-center:before { content: "\f134" !important; }
79
+ .dashicons-align-none:before { content: "\f138" !important; }
80
+ .dashicons-lock:before { content: "\f160" !important; }
81
+ .dashicons-unlock:before { content: "\f528" !important; }
82
+ .dashicons-calendar:before { content: "\f145" !important; }
83
+ .dashicons-calendar-alt:before { content: "\f508" !important; }
84
+ .dashicons-visibility:before { content: "\f177" !important; }
85
+ .dashicons-hidden:before { content: "\f530" !important; }
86
+ .dashicons-post-status:before { content: "\f173" !important; }
87
+ .dashicons-edit:before { content: "\f464" !important; }
88
+ .dashicons-post-trash:before, .dashicons-trash:before { content: "\f182" !important; }
89
+ .dashicons-sticky:before { content: "\f537" !important; }
90
+ .dashicons-external:before { content: "\f504" !important; }
91
+ .dashicons-leftright:before { content: "\f229" !important; }
92
+ .dashicons-sort:before { content: "\f156" !important; }
93
+ .dashicons-randomize:before { content: "\f503" !important; }
94
+ .dashicons-list-view:before { content: "\f163" !important; }
95
+ .dashicons-exerpt-view:before, .dashicons-excerpt-view:before { content: "\f164" !important; }
96
+ .dashicons-grid-view:before { content: "\f509" !important; }
97
+ .dashicons-move:before { content: "\f545" !important; }
98
+ .dashicons-hammer:before { content: "\f308" !important; }
99
+ .dashicons-art:before { content: "\f309" !important; }
100
+ .dashicons-migrate:before { content: "\f310" !important; }
101
+ .dashicons-performance:before { content: "\f311" !important; }
102
+ .dashicons-universal-access:before { content: "\f483" !important; }
103
+ .dashicons-universal-access-alt:before { content: "\f507" !important; }
104
+ .dashicons-tickets:before { content: "\f486" !important; }
105
+ .dashicons-nametag:before { content: "\f484" !important; }
106
+ .dashicons-clipboard:before { content: "\f481" !important; }
107
+ .dashicons-heart:before { content: "\f487" !important; }
108
+ .dashicons-megaphone:before { content: "\f488" !important; }
109
+ .dashicons-schedule:before { content: "\f489" !important; }
110
+ .dashicons-wordpress:before { content: "\f120" !important; }
111
+ .dashicons-wordpress-alt:before { content: "\f324" !important; }
112
+ .dashicons-pressthis:before { content: "\f157" !important; }
113
+ .dashicons-update:before { content: "\f463" !important; }
114
+ .dashicons-screenoptions:before { content: "\f180" !important; }
115
+ .dashicons-cart:before { content: "\f174" !important; }
116
+ .dashicons-feedback:before { content: "\f175" !important; }
117
+ .dashicons-cloud:before { content: "\f176" !important; }
118
+ .dashicons-translation:before { content: "\f326" !important; }
119
+ .dashicons-tag:before { content: "\f323" !important; }
120
+ .dashicons-category:before { content: "\f318" !important; }
121
+ .dashicons-archive:before { content: "\f480" !important; }
122
+ .dashicons-tagcloud:before { content: "\f479" !important; }
123
+ .dashicons-text:before { content: "\f478" !important; }
124
+ .dashicons-media-archive:before { content: "\f501" !important; }
125
+ .dashicons-media-audio:before { content: "\f500" !important; }
126
+ .dashicons-media-code:before { content: "\f499" !important; }
127
+ .dashicons-media-default:before { content: "\f498" !important; }
128
+ .dashicons-media-document:before { content: "\f497" !important; }
129
+ .dashicons-media-interactive:before { content: "\f496" !important; }
130
+ .dashicons-media-spreadsheet:before { content: "\f495" !important; }
131
+ .dashicons-media-text:before { content: "\f491" !important; }
132
+ .dashicons-media-video:before { content: "\f490" !important; }
133
+ .dashicons-playlist-audio:before { content: "\f492" !important; }
134
+ .dashicons-playlist-video:before { content: "\f493" !important; }
135
+ .dashicons-controls-play:before { content: "\f522" !important; }
136
+ .dashicons-controls-pause:before { content: "\f523" !important; }
137
+ .dashicons-controls-forward:before { content: "\f519" !important; }
138
+ .dashicons-controls-skipforward:before { content: "\f517" !important; }
139
+ .dashicons-controls-back:before { content: "\f518" !important; }
140
+ .dashicons-controls-skipback:before { content: "\f516" !important; }
141
+ .dashicons-controls-repeat:before { content: "\f515" !important; }
142
+ .dashicons-controls-volumeon:before { content: "\f521" !important; }
143
+ .dashicons-controls-volumeoff:before { content: "\f520" !important; }
144
+ .dashicons-yes:before { content: "\f147" !important; }
145
+ .dashicons-no:before { content: "\f158" !important; }
146
+ .dashicons-no-alt:before { content: "\f335" !important; }
147
+ .dashicons-plus:before { content: "\f132" !important; }
148
+ .dashicons-plus-alt:before { content: "\f502" !important; }
149
+ .dashicons-plus-alt2:before { content: "\f543" !important; }
150
+ .dashicons-minus:before { content: "\f460" !important; }
151
+ .dashicons-dismiss:before { content: "\f153" !important; }
152
+ .dashicons-marker:before { content: "\f159" !important; }
153
+ .dashicons-star-filled:before { content: "\f155" !important; }
154
+ .dashicons-star-half:before { content: "\f459" !important; }
155
+ .dashicons-star-empty:before { content: "\f154" !important; }
156
+ .dashicons-flag:before { content: "\f227" !important; }
157
+ .dashicons-info:before { content: "\f348" !important; }
158
+ .dashicons-warning:before { content: "\f534" !important; }
159
+ .dashicons-share:before { content: "\f237" !important; }
160
+ .dashicons-share1:before { content: "\f237" !important; }
161
+ .dashicons-share-alt:before { content: "\f240" !important; }
162
+ .dashicons-share-alt2:before { content: "\f242" !important; }
163
+ .dashicons-twitter:before { content: "\f301" !important; }
164
+ .dashicons-rss:before { content: "\f303" !important; }
165
+ .dashicons-email:before { content: "\f465" !important; }
166
+ .dashicons-email-alt:before { content: "\f466" !important; }
167
+ .dashicons-facebook:before { content: "\f304" !important; }
168
+ .dashicons-facebook-alt:before { content: "\f305" !important; }
169
+ .dashicons-networking:before { content: "\f325" !important; }
170
+ .dashicons-googleplus:before { content: "\f462" !important; }
171
+ .dashicons-location:before { content: "\f230" !important; }
172
+ .dashicons-location-alt:before { content: "\f231" !important; }
173
+ .dashicons-camera:before { content: "\f306" !important; }
174
+ .dashicons-images-alt:before { content: "\f232" !important; }
175
+ .dashicons-images-alt2:before { content: "\f233" !important; }
176
+ .dashicons-video-alt:before { content: "\f234" !important; }
177
+ .dashicons-video-alt2:before { content: "\f235" !important; }
178
+ .dashicons-video-alt3:before { content: "\f236" !important; }
179
+ .dashicons-vault:before { content: "\f178" !important; }
180
+ .dashicons-shield:before { content: "\f332" !important; }
181
+ .dashicons-shield-alt:before { content: "\f334" !important; }
182
+ .dashicons-sos:before { content: "\f468" !important; }
183
+ .dashicons-search:before { content: "\f179" !important; }
184
+ .dashicons-slides:before { content: "\f181" !important; }
185
+ .dashicons-analytics:before { content: "\f183" !important; }
186
+ .dashicons-chart-pie:before { content: "\f184" !important; }
187
+ .dashicons-chart-bar:before { content: "\f185" !important; }
188
+ .dashicons-chart-line:before { content: "\f238" !important; }
189
+ .dashicons-chart-area:before { content: "\f239" !important; }
190
+ .dashicons-groups:before { content: "\f307" !important; }
191
+ .dashicons-businessman:before { content: "\f338" !important; }
192
+ .dashicons-id:before { content: "\f336" !important; }
193
+ .dashicons-id-alt:before { content: "\f337" !important; }
194
+ .dashicons-products:before { content: "\f312" !important; }
195
+ .dashicons-awards:before { content: "\f313" !important; }
196
+ .dashicons-forms:before { content: "\f314" !important; }
197
+ .dashicons-testimonial:before { content: "\f473" !important; }
198
+ .dashicons-portfolio:before { content: "\f322" !important; }
199
+ .dashicons-book:before { content: "\f330" !important; }
200
+ .dashicons-book-alt:before { content: "\f331" !important; }
201
+ .dashicons-download:before { content: "\f316" !important; }
202
+ .dashicons-upload:before { content: "\f317" !important; }
203
+ .dashicons-backup:before { content: "\f321" !important; }
204
+ .dashicons-clock:before { content: "\f469" !important; }
205
+ .dashicons-lightbulb:before { content: "\f339" !important; }
206
+ .dashicons-microphone:before { content: "\f482" !important; }
207
+ .dashicons-desktop:before { content: "\f472" !important; }
208
+ .dashicons-laptop:before { content: "\f547" !important; }
209
+ .dashicons-tablet:before { content: "\f471" !important; }
210
+ .dashicons-smartphone:before { content: "\f470" !important; }
211
+ .dashicons-phone:before { content: "\f525" !important; }
212
+ .dashicons-smiley:before { content: "\f328" !important; }
213
+ .dashicons-index-card:before { content: "\f510" !important; }
214
+ .dashicons-carrot:before { content: "\f511" !important; }
215
+ .dashicons-building:before { content: "\f512" !important; }
216
+ .dashicons-store:before { content: "\f513" !important; }
217
+ .dashicons-album:before { content: "\f514" !important; }
218
+ .dashicons-palmtree:before { content: "\f527" !important; }
219
+ .dashicons-tickets-alt:before { content: "\f524" !important; }
220
+ .dashicons-money:before { content: "\f526" !important; }
221
+ .dashicons-thumbs-up:before { content: "\f529" !important; }
222
+ .dashicons-thumbs-down:before { content: "\f542" !important; }
223
+ .dashicons-layout:before { content: "\f538" !important; }
224
+ .dashicons-paperclip:before { content: "\f546" !important; }
css/force-dashicons.css ADDED
@@ -0,0 +1,460 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Forcibly set menu icons to the selected custom Dashicons.
3
+
4
+ Problem:
5
+ Some plugins use CSS to assign icons to their admin menu items. Users want to change the icons.
6
+ In many cases, simply changing the icon URL doesn't work because the plugin CSS overrides it.
7
+
8
+ Workaround:
9
+ Add more CSS that overrides the icon styles that were set by other plugins.
10
+ */
11
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon {
12
+ /*
13
+ This file was automatically generated from /wp-includes/css/dashicons.css.
14
+ Last update: 2017-06-07T16:55:55+00:00
15
+ */ }
16
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon > .wp-menu-image::before {
17
+ font-family: "dashicons", sans-serif !important;
18
+ font-size: 20px !important; }
19
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-menu:before {
20
+ content: "\f333" !important; }
21
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-site:before {
22
+ content: "\f319" !important; }
23
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-media:before {
24
+ content: "\f104" !important; }
25
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-page:before {
26
+ content: "\f105" !important; }
27
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-comments:before {
28
+ content: "\f101" !important; }
29
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-appearance:before {
30
+ content: "\f100" !important; }
31
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-plugins:before {
32
+ content: "\f106" !important; }
33
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-users:before {
34
+ content: "\f110" !important; }
35
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-tools:before {
36
+ content: "\f107" !important; }
37
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-settings:before {
38
+ content: "\f108" !important; }
39
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-network:before {
40
+ content: "\f112" !important; }
41
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-generic:before {
42
+ content: "\f111" !important; }
43
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-home:before {
44
+ content: "\f102" !important; }
45
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-collapse:before {
46
+ content: "\f148" !important; }
47
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-filter:before {
48
+ content: "\f536" !important; }
49
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-customizer:before {
50
+ content: "\f540" !important; }
51
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-multisite:before {
52
+ content: "\f541" !important; }
53
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-links:before, #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-links:before {
54
+ content: "\f103" !important; }
55
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-admin-post:before, #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-standard:before {
56
+ content: "\f109" !important; }
57
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-image:before {
58
+ content: "\f128" !important; }
59
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-gallery:before {
60
+ content: "\f161" !important; }
61
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-audio:before {
62
+ content: "\f127" !important; }
63
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-video:before {
64
+ content: "\f126" !important; }
65
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-chat:before {
66
+ content: "\f125" !important; }
67
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-status:before {
68
+ content: "\f130" !important; }
69
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-aside:before {
70
+ content: "\f123" !important; }
71
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-format-quote:before {
72
+ content: "\f122" !important; }
73
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-welcome-write-blog:before, #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-welcome-edit-page:before {
74
+ content: "\f119" !important; }
75
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-welcome-add-page:before {
76
+ content: "\f133" !important; }
77
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-welcome-view-site:before {
78
+ content: "\f115" !important; }
79
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-welcome-widgets-menus:before {
80
+ content: "\f116" !important; }
81
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-welcome-comments:before {
82
+ content: "\f117" !important; }
83
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-welcome-learn-more:before {
84
+ content: "\f118" !important; }
85
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-image-crop:before {
86
+ content: "\f165" !important; }
87
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-image-rotate:before {
88
+ content: "\f531" !important; }
89
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-image-rotate-left:before {
90
+ content: "\f166" !important; }
91
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-image-rotate-right:before {
92
+ content: "\f167" !important; }
93
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-image-flip-vertical:before {
94
+ content: "\f168" !important; }
95
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-image-flip-horizontal:before {
96
+ content: "\f169" !important; }
97
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-image-filter:before {
98
+ content: "\f533" !important; }
99
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-undo:before {
100
+ content: "\f171" !important; }
101
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-redo:before {
102
+ content: "\f172" !important; }
103
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-ul:before {
104
+ content: "\f203" !important; }
105
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-ol:before {
106
+ content: "\f204" !important; }
107
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-quote:before {
108
+ content: "\f205" !important; }
109
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-alignleft:before {
110
+ content: "\f206" !important; }
111
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-aligncenter:before {
112
+ content: "\f207" !important; }
113
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-alignright:before {
114
+ content: "\f208" !important; }
115
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-insertmore:before {
116
+ content: "\f209" !important; }
117
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-spellcheck:before {
118
+ content: "\f210" !important; }
119
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-distractionfree:before, #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-expand:before {
120
+ content: "\f211" !important; }
121
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-contract:before {
122
+ content: "\f506" !important; }
123
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-kitchensink:before {
124
+ content: "\f212" !important; }
125
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-underline:before {
126
+ content: "\f213" !important; }
127
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-justify:before {
128
+ content: "\f214" !important; }
129
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-textcolor:before {
130
+ content: "\f215" !important; }
131
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-paste-word:before {
132
+ content: "\f216" !important; }
133
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-paste-text:before {
134
+ content: "\f217" !important; }
135
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-removeformatting:before {
136
+ content: "\f218" !important; }
137
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-video:before {
138
+ content: "\f219" !important; }
139
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-customchar:before {
140
+ content: "\f220" !important; }
141
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-outdent:before {
142
+ content: "\f221" !important; }
143
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-indent:before {
144
+ content: "\f222" !important; }
145
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-help:before {
146
+ content: "\f223" !important; }
147
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-strikethrough:before {
148
+ content: "\f224" !important; }
149
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-unlink:before {
150
+ content: "\f225" !important; }
151
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-rtl:before {
152
+ content: "\f320" !important; }
153
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-break:before {
154
+ content: "\f474" !important; }
155
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-code:before {
156
+ content: "\f475" !important; }
157
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-paragraph:before {
158
+ content: "\f476" !important; }
159
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-editor-table:before {
160
+ content: "\f535" !important; }
161
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-align-left:before {
162
+ content: "\f135" !important; }
163
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-align-right:before {
164
+ content: "\f136" !important; }
165
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-align-center:before {
166
+ content: "\f134" !important; }
167
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-align-none:before {
168
+ content: "\f138" !important; }
169
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-lock:before {
170
+ content: "\f160" !important; }
171
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-unlock:before {
172
+ content: "\f528" !important; }
173
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-calendar:before {
174
+ content: "\f145" !important; }
175
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-calendar-alt:before {
176
+ content: "\f508" !important; }
177
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-visibility:before {
178
+ content: "\f177" !important; }
179
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-hidden:before {
180
+ content: "\f530" !important; }
181
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-post-status:before {
182
+ content: "\f173" !important; }
183
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-edit:before {
184
+ content: "\f464" !important; }
185
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-post-trash:before, #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-trash:before {
186
+ content: "\f182" !important; }
187
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-sticky:before {
188
+ content: "\f537" !important; }
189
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-external:before {
190
+ content: "\f504" !important; }
191
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-leftright:before {
192
+ content: "\f229" !important; }
193
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-sort:before {
194
+ content: "\f156" !important; }
195
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-randomize:before {
196
+ content: "\f503" !important; }
197
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-list-view:before {
198
+ content: "\f163" !important; }
199
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-exerpt-view:before, #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-excerpt-view:before {
200
+ content: "\f164" !important; }
201
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-grid-view:before {
202
+ content: "\f509" !important; }
203
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-move:before {
204
+ content: "\f545" !important; }
205
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-hammer:before {
206
+ content: "\f308" !important; }
207
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-art:before {
208
+ content: "\f309" !important; }
209
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-migrate:before {
210
+ content: "\f310" !important; }
211
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-performance:before {
212
+ content: "\f311" !important; }
213
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-universal-access:before {
214
+ content: "\f483" !important; }
215
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-universal-access-alt:before {
216
+ content: "\f507" !important; }
217
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-tickets:before {
218
+ content: "\f486" !important; }
219
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-nametag:before {
220
+ content: "\f484" !important; }
221
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-clipboard:before {
222
+ content: "\f481" !important; }
223
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-heart:before {
224
+ content: "\f487" !important; }
225
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-megaphone:before {
226
+ content: "\f488" !important; }
227
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-schedule:before {
228
+ content: "\f489" !important; }
229
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-wordpress:before {
230
+ content: "\f120" !important; }
231
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-wordpress-alt:before {
232
+ content: "\f324" !important; }
233
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-pressthis:before {
234
+ content: "\f157" !important; }
235
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-update:before {
236
+ content: "\f463" !important; }
237
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-screenoptions:before {
238
+ content: "\f180" !important; }
239
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-cart:before {
240
+ content: "\f174" !important; }
241
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-feedback:before {
242
+ content: "\f175" !important; }
243
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-cloud:before {
244
+ content: "\f176" !important; }
245
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-translation:before {
246
+ content: "\f326" !important; }
247
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-tag:before {
248
+ content: "\f323" !important; }
249
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-category:before {
250
+ content: "\f318" !important; }
251
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-archive:before {
252
+ content: "\f480" !important; }
253
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-tagcloud:before {
254
+ content: "\f479" !important; }
255
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-text:before {
256
+ content: "\f478" !important; }
257
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-archive:before {
258
+ content: "\f501" !important; }
259
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-audio:before {
260
+ content: "\f500" !important; }
261
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-code:before {
262
+ content: "\f499" !important; }
263
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-default:before {
264
+ content: "\f498" !important; }
265
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-document:before {
266
+ content: "\f497" !important; }
267
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-interactive:before {
268
+ content: "\f496" !important; }
269
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-spreadsheet:before {
270
+ content: "\f495" !important; }
271
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-text:before {
272
+ content: "\f491" !important; }
273
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-media-video:before {
274
+ content: "\f490" !important; }
275
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-playlist-audio:before {
276
+ content: "\f492" !important; }
277
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-playlist-video:before {
278
+ content: "\f493" !important; }
279
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-play:before {
280
+ content: "\f522" !important; }
281
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-pause:before {
282
+ content: "\f523" !important; }
283
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-forward:before {
284
+ content: "\f519" !important; }
285
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-skipforward:before {
286
+ content: "\f517" !important; }
287
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-back:before {
288
+ content: "\f518" !important; }
289
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-skipback:before {
290
+ content: "\f516" !important; }
291
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-repeat:before {
292
+ content: "\f515" !important; }
293
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-volumeon:before {
294
+ content: "\f521" !important; }
295
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-controls-volumeoff:before {
296
+ content: "\f520" !important; }
297
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-yes:before {
298
+ content: "\f147" !important; }
299
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-no:before {
300
+ content: "\f158" !important; }
301
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-no-alt:before {
302
+ content: "\f335" !important; }
303
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-plus:before {
304
+ content: "\f132" !important; }
305
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-plus-alt:before {
306
+ content: "\f502" !important; }
307
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-plus-alt2:before {
308
+ content: "\f543" !important; }
309
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-minus:before {
310
+ content: "\f460" !important; }
311
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-dismiss:before {
312
+ content: "\f153" !important; }
313
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-marker:before {
314
+ content: "\f159" !important; }
315
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-star-filled:before {
316
+ content: "\f155" !important; }
317
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-star-half:before {
318
+ content: "\f459" !important; }
319
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-star-empty:before {
320
+ content: "\f154" !important; }
321
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-flag:before {
322
+ content: "\f227" !important; }
323
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-info:before {
324
+ content: "\f348" !important; }
325
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-warning:before {
326
+ content: "\f534" !important; }
327
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-share:before {
328
+ content: "\f237" !important; }
329
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-share1:before {
330
+ content: "\f237" !important; }
331
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-share-alt:before {
332
+ content: "\f240" !important; }
333
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-share-alt2:before {
334
+ content: "\f242" !important; }
335
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-twitter:before {
336
+ content: "\f301" !important; }
337
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-rss:before {
338
+ content: "\f303" !important; }
339
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-email:before {
340
+ content: "\f465" !important; }
341
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-email-alt:before {
342
+ content: "\f466" !important; }
343
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-facebook:before {
344
+ content: "\f304" !important; }
345
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-facebook-alt:before {
346
+ content: "\f305" !important; }
347
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-networking:before {
348
+ content: "\f325" !important; }
349
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-googleplus:before {
350
+ content: "\f462" !important; }
351
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-location:before {
352
+ content: "\f230" !important; }
353
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-location-alt:before {
354
+ content: "\f231" !important; }
355
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-camera:before {
356
+ content: "\f306" !important; }
357
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-images-alt:before {
358
+ content: "\f232" !important; }
359
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-images-alt2:before {
360
+ content: "\f233" !important; }
361
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-video-alt:before {
362
+ content: "\f234" !important; }
363
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-video-alt2:before {
364
+ content: "\f235" !important; }
365
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-video-alt3:before {
366
+ content: "\f236" !important; }
367
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-vault:before {
368
+ content: "\f178" !important; }
369
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-shield:before {
370
+ content: "\f332" !important; }
371
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-shield-alt:before {
372
+ content: "\f334" !important; }
373
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-sos:before {
374
+ content: "\f468" !important; }
375
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-search:before {
376
+ content: "\f179" !important; }
377
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-slides:before {
378
+ content: "\f181" !important; }
379
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-analytics:before {
380
+ content: "\f183" !important; }
381
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-chart-pie:before {
382
+ content: "\f184" !important; }
383
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-chart-bar:before {
384
+ content: "\f185" !important; }
385
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-chart-line:before {
386
+ content: "\f238" !important; }
387
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-chart-area:before {
388
+ content: "\f239" !important; }
389
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-groups:before {
390
+ content: "\f307" !important; }
391
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-businessman:before {
392
+ content: "\f338" !important; }
393
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-id:before {
394
+ content: "\f336" !important; }
395
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-id-alt:before {
396
+ content: "\f337" !important; }
397
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-products:before {
398
+ content: "\f312" !important; }
399
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-awards:before {
400
+ content: "\f313" !important; }
401
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-forms:before {
402
+ content: "\f314" !important; }
403
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-testimonial:before {
404
+ content: "\f473" !important; }
405
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-portfolio:before {
406
+ content: "\f322" !important; }
407
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-book:before {
408
+ content: "\f330" !important; }
409
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-book-alt:before {
410
+ content: "\f331" !important; }
411
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-download:before {
412
+ content: "\f316" !important; }
413
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-upload:before {
414
+ content: "\f317" !important; }
415
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-backup:before {
416
+ content: "\f321" !important; }
417
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-clock:before {
418
+ content: "\f469" !important; }
419
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-lightbulb:before {
420
+ content: "\f339" !important; }
421
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-microphone:before {
422
+ content: "\f482" !important; }
423
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-desktop:before {
424
+ content: "\f472" !important; }
425
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-laptop:before {
426
+ content: "\f547" !important; }
427
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-tablet:before {
428
+ content: "\f471" !important; }
429
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-smartphone:before {
430
+ content: "\f470" !important; }
431
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-phone:before {
432
+ content: "\f525" !important; }
433
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-smiley:before {
434
+ content: "\f328" !important; }
435
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-index-card:before {
436
+ content: "\f510" !important; }
437
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-carrot:before {
438
+ content: "\f511" !important; }
439
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-building:before {
440
+ content: "\f512" !important; }
441
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-store:before {
442
+ content: "\f513" !important; }
443
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-album:before {
444
+ content: "\f514" !important; }
445
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-palmtree:before {
446
+ content: "\f527" !important; }
447
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-tickets-alt:before {
448
+ content: "\f524" !important; }
449
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-money:before {
450
+ content: "\f526" !important; }
451
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-thumbs-up:before {
452
+ content: "\f529" !important; }
453
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-thumbs-down:before {
454
+ content: "\f542" !important; }
455
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-layout:before {
456
+ content: "\f538" !important; }
457
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon .dashicons-paperclip:before {
458
+ content: "\f546" !important; }
459
+
460
+ /*# sourceMappingURL=force-dashicons.css.map */
css/force-dashicons.css.map ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ {
2
+ "version": 3,
3
+ "mappings": "AAAA;;;;;;;;;EASE;AAGF,wDAAyD;ECZzD;;;IAGE;EDUD,iFAA2B;IAC1B,WAAW,EAAE,kCAAkC;IAC/C,SAAS,EAAE,eAAe;ECX5B,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,0FAAkC;IAAE,OAAO,EAAE,kBAAkB;EAC/D,+KAA8D;IAAE,OAAO,EAAE,kBAAkB;EAC3F,iLAAgE;IAAE,OAAO,EAAE,kBAAkB;EAC7F,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,2LAA0E;IAAE,OAAO,EAAE,kBAAkB;EACvG,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,gGAAwC;IAAE,OAAO,EAAE,kBAAkB;EACrE,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,6FAAqC;IAAE,OAAO,EAAE,kBAAkB;EAClE,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,6FAAqC;IAAE,OAAO,EAAE,kBAAkB;EAClE,8FAAsC;IAAE,OAAO,EAAE,kBAAkB;EACnE,gGAAwC;IAAE,OAAO,EAAE,kBAAkB;EACrE,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,6FAAqC;IAAE,OAAO,EAAE,kBAAkB;EAClE,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,2LAA0E;IAAE,OAAO,EAAE,kBAAkB;EACvG,0FAAkC;IAAE,OAAO,EAAE,kBAAkB;EAC/D,6FAAqC;IAAE,OAAO,EAAE,kBAAkB;EAClE,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,kGAA0C;IAAE,OAAO,EAAE,kBAAkB;EACvE,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,+FAAuC;IAAE,OAAO,EAAE,kBAAkB;EACpE,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,uKAAsD;IAAE,OAAO,EAAE,kBAAkB;EACnF,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,+KAA8D;IAAE,OAAO,EAAE,kBAAkB;EAC3F,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,8EAAsB;IAAE,OAAO,EAAE,kBAAkB;EACnD,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,+FAAuC;IAAE,OAAO,EAAE,kBAAkB;EACpE,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,8EAAsB;IAAE,OAAO,EAAE,kBAAkB;EACnD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,yFAAiC;IAAE,OAAO,EAAE,kBAAkB;EAC9D,2FAAmC;IAAE,OAAO,EAAE,kBAAkB;EAChE,+FAAuC;IAAE,OAAO,EAAE,kBAAkB;EACpE,wFAAgC;IAAE,OAAO,EAAE,kBAAkB;EAC7D,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,0FAAkC;IAAE,OAAO,EAAE,kBAAkB;EAC/D,4FAAoC;IAAE,OAAO,EAAE,kBAAkB;EACjE,6FAAqC;IAAE,OAAO,EAAE,kBAAkB;EAClE,8EAAsB;IAAE,OAAO,EAAE,kBAAkB;EACnD,6EAAqB;IAAE,OAAO,EAAE,kBAAkB;EAClD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,8EAAsB;IAAE,OAAO,EAAE,kBAAkB;EACnD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,uFAA+B;IAAE,OAAO,EAAE,kBAAkB;EAC5D,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,8EAAsB;IAAE,OAAO,EAAE,kBAAkB;EACnD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,6EAAqB;IAAE,OAAO,EAAE,kBAAkB;EAClD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,+EAAuB;IAAE,OAAO,EAAE,kBAAkB;EACpD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,kFAA0B;IAAE,OAAO,EAAE,kBAAkB;EACvD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,qFAA6B;IAAE,OAAO,EAAE,kBAAkB;EAC1D,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,mFAA2B;IAAE,OAAO,EAAE,kBAAkB;EACxD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,gFAAwB;IAAE,OAAO,EAAE,kBAAkB;EACrD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB;EACzD,sFAA8B;IAAE,OAAO,EAAE,kBAAkB;EAC3D,iFAAyB;IAAE,OAAO,EAAE,kBAAkB;EACtD,oFAA4B;IAAE,OAAO,EAAE,kBAAkB",
4
+ "sources": ["force-dashicons.scss","_dashicons.scss"],
5
+ "names": [],
6
+ "file": "force-dashicons.css"
7
+ }
css/force-dashicons.scss ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Forcibly set menu icons to the selected custom Dashicons.
3
+
4
+ Problem:
5
+ Some plugins use CSS to assign icons to their admin menu items. Users want to change the icons.
6
+ In many cases, simply changing the icon URL doesn't work because the plugin CSS overrides it.
7
+
8
+ Workaround:
9
+ Add more CSS that overrides the icon styles that were set by other plugins.
10
+ */
11
+
12
+ //Artificially increase selector specificity by repeating the ID.
13
+ #adminmenu#adminmenu#adminmenu a.ame-has-custom-dashicon {
14
+ & > .wp-menu-image::before {
15
+ font-family: "dashicons", sans-serif !important;
16
+ font-size: 20px !important;
17
+ }
18
+
19
+ @import '_dashicons';
20
+ }
css/menu-editor.css CHANGED
@@ -144,6 +144,9 @@
144
  width: 290px;
145
  padding: 3px;
146
  margin: 2px 0 2px 6px; }
 
 
 
147
 
148
  .ws_submenu {
149
  min-height: 2em; }
@@ -530,8 +533,8 @@ select.ws_dropdown optgroup option {
530
  margin-bottom: 0; }
531
  .ws_tool_tab_nav li.ui-tabs-active {
532
  border-color: #dfdfdf;
533
- background-color: #FDFDFD;
534
- border-bottom-color: #FDFDFD; }
535
  .ws_tool_tab_nav a {
536
  text-decoration: none; }
537
  .ws_tool_tab_nav li.ui-tabs-active a {
@@ -837,6 +840,9 @@ a#ws-ame-delete-color-preset:hover {
837
  -moz-border-radius: 5px;
838
  -webkit-border-radius: 5px;
839
  border-radius: 5px; }
 
 
 
840
 
841
  .ui-dialog-titlebar {
842
  display: block;
@@ -873,10 +879,6 @@ a#ws-ame-delete-color-preset:hover {
873
  /*background-image: url(../images/x-light.png);*/
874
  background-color: #a6c2f5; }
875
 
876
- .ui-dialog-content {
877
- padding: 8px 8px 8px 8px;
878
- font-size: 1.1em; }
879
-
880
  #export_dialog .ws_dialog_panel {
881
  height: 50px; }
882
 
@@ -1370,6 +1372,17 @@ a#ws-ame-delete-color-preset:hover {
1370
  #ws_capability_suggestions .ws_ame_suggested_capability:hover {
1371
  background-color: #d0f2d0; }
1372
 
 
 
 
 
 
 
 
 
 
 
 
1373
  #ws_sidebar_pro_ad {
1374
  min-width: 225px;
1375
  margin-top: 5px;
144
  width: 290px;
145
  padding: 3px;
146
  margin: 2px 0 2px 6px; }
147
+ body.rtl .ws_container {
148
+ margin-right: 6px;
149
+ margin-left: 0; }
150
 
151
  .ws_submenu {
152
  min-height: 2em; }
533
  margin-bottom: 0; }
534
  .ws_tool_tab_nav li.ui-tabs-active {
535
  border-color: #dfdfdf;
536
+ border-bottom-color: #FDFDFD;
537
+ background: #FDFDFD none; }
538
  .ws_tool_tab_nav a {
539
  text-decoration: none; }
540
  .ws_tool_tab_nav li.ui-tabs-active a {
840
  -moz-border-radius: 5px;
841
  -webkit-border-radius: 5px;
842
  border-radius: 5px; }
843
+ .ui-dialog .ui-dialog-content {
844
+ padding: 8px 8px 8px 8px;
845
+ font-size: 1.1em; }
846
 
847
  .ui-dialog-titlebar {
848
  display: block;
879
  /*background-image: url(../images/x-light.png);*/
880
  background-color: #a6c2f5; }
881
 
 
 
 
 
882
  #export_dialog .ws_dialog_panel {
883
  height: 50px; }
884
 
1372
  #ws_capability_suggestions .ws_ame_suggested_capability:hover {
1373
  background-color: #d0f2d0; }
1374
 
1375
+ /*********************************************
1376
+ Settings page stuff
1377
+ **********************************************/
1378
+ #ws_plugin_settings_form figure {
1379
+ margin-left: 0;
1380
+ margin-top: 0;
1381
+ margin-bottom: 1em; }
1382
+
1383
+ /*********************************************
1384
+ Miscellaneous
1385
+ **********************************************/
1386
  #ws_sidebar_pro_ad {
1387
  min-width: 225px;
1388
  margin-top: 5px;
css/menu-editor.scss CHANGED
@@ -215,6 +215,11 @@ $mainContainerBorderRadius: 0px;
215
 
216
  padding : $itemPadding;
217
  margin: 2px 0 2px $itemHorizontalMargin;
 
 
 
 
 
218
  }
219
 
220
  .ws_active { }
@@ -731,8 +736,8 @@ $activeToolTabBackground: #FDFDFD;
731
 
732
  li.ui-tabs-active {
733
  border-color: #dfdfdf;
734
- background-color: #FDFDFD;
735
  border-bottom-color: $activeToolTabBackground;
 
736
  }
737
 
738
  a {
@@ -1130,6 +1135,11 @@ a#ws-ame-delete-color-preset:hover {
1130
  -moz-border-radius: 5px;
1131
  -webkit-border-radius: 5px;
1132
  border-radius: 5px;
 
 
 
 
 
1133
  }
1134
 
1135
  .ui-dialog-titlebar {
@@ -1181,11 +1191,6 @@ a#ws-ame-delete-color-preset:hover {
1181
 
1182
  }
1183
 
1184
- .ui-dialog-content {
1185
- padding: 8px 8px 8px 8px;
1186
- font-size: 1.1em;
1187
- }
1188
-
1189
  #export_dialog .ws_dialog_panel {
1190
  height: 50px;
1191
  }
@@ -1933,6 +1938,19 @@ $userSelectionPanelPadding: 10px;
1933
  }
1934
  }
1935
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1936
 
1937
  #ws_sidebar_pro_ad {
1938
  min-width: 225px;
215
 
216
  padding : $itemPadding;
217
  margin: 2px 0 2px $itemHorizontalMargin;
218
+
219
+ body.rtl & {
220
+ margin-right: $itemHorizontalMargin;
221
+ margin-left: 0;
222
+ }
223
  }
224
 
225
  .ws_active { }
736
 
737
  li.ui-tabs-active {
738
  border-color: #dfdfdf;
 
739
  border-bottom-color: $activeToolTabBackground;
740
+ background: #FDFDFD none;
741
  }
742
 
743
  a {
1135
  -moz-border-radius: 5px;
1136
  -webkit-border-radius: 5px;
1137
  border-radius: 5px;
1138
+
1139
+ .ui-dialog-content {
1140
+ padding: 8px 8px 8px 8px;
1141
+ font-size: 1.1em;
1142
+ }
1143
  }
1144
 
1145
  .ui-dialog-titlebar {
1191
 
1192
  }
1193
 
 
 
 
 
 
1194
  #export_dialog .ws_dialog_panel {
1195
  height: 50px;
1196
  }
1938
  }
1939
  }
1940
 
1941
+ /*********************************************
1942
+ Settings page stuff
1943
+ **********************************************/
1944
+
1945
+ #ws_plugin_settings_form figure {
1946
+ margin-left: 0;
1947
+ margin-top: 0;
1948
+ margin-bottom: 1em;
1949
+ }
1950
+
1951
+ /*********************************************
1952
+ Miscellaneous
1953
+ **********************************************/
1954
 
1955
  #ws_sidebar_pro_ad {
1956
  min-width: 225px;
css/screen-meta.css CHANGED
@@ -12,8 +12,8 @@
12
  border: 1px solid #ddd;
13
  border-top: none;
14
  background: #fff;
15
- -webkit-box-shadow: 0px 1px 1px -1px rgba(0,0,0,0.1);
16
- box-shadow: 0px 1px 1px -1px rgba(0,0,0,0.1);
17
  }
18
 
19
  #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
@@ -23,6 +23,7 @@
23
  text-decoration: none;
24
  display: block;
25
  min-height: 22px;
 
26
  }
27
 
28
  #screen-meta-links a.custom-screen-meta-link::after {
@@ -35,10 +36,11 @@
35
  border-color: #0a0;
36
  }
37
 
38
- #ws-pro-version-notice a.show-settings {
39
  font-weight: bold;
40
  color: #DEFFD8;
41
  text-shadow: none;
 
42
  }
43
 
44
  #ws-pro-version-notice a.show-settings:hover {
12
  border: 1px solid #ddd;
13
  border-top: none;
14
  background: #fff;
15
+ -webkit-box-shadow: 0 1px 1px -1px rgba(0,0,0,0.1);
16
+ box-shadow: 0 1px 1px -1px rgba(0,0,0,0.1);
17
  }
18
 
19
  #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
23
  text-decoration: none;
24
  display: block;
25
  min-height: 22px;
26
+ box-shadow: 0 1px 0 #cccccc;
27
  }
28
 
29
  #screen-meta-links a.custom-screen-meta-link::after {
36
  border-color: #0a0;
37
  }
38
 
39
+ #screen-meta-links #ws-pro-version-notice a.show-settings {
40
  font-weight: bold;
41
  color: #DEFFD8;
42
  text-shadow: none;
43
+ box-shadow: none;
44
  }
45
 
46
  #ws-pro-version-notice a.show-settings:hover {
css/style-modern-one.css CHANGED
@@ -39,16 +39,16 @@ $hiddenItemBorder: #f1acb1;
39
  -moz-border-radius: 0;
40
  border-radius: 0; }
41
 
42
- .ws_edit_link {
43
  background: transparent;
44
  color: #A0A5AA;
45
  text-align: center;
46
  border-radius: 0; }
47
- .ws_edit_link::before {
48
  content: "\f140";
49
  font: normal 20px/1 dashicons;
50
  display: block; }
51
- .ws_edit_link:hover {
52
  color: #777; }
53
 
54
  .ws_edit_link.ws_edit_link_expanded::before {
@@ -147,6 +147,30 @@ $hiddenItemBorder: #f1acb1;
147
  border-bottom: 1px solid #dfdfdf;
148
  height: auto;
149
  padding: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  .ui-dialog-title {
152
  color: #444444;
@@ -156,28 +180,4 @@ $hiddenItemBorder: #f1acb1;
156
  padding: 0 36px 0 8px;
157
  display: block; }
158
 
159
- .ui-dialog-titlebar-close {
160
- background: none;
161
- border-style: none;
162
- color: #666;
163
- cursor: pointer;
164
- padding: 0;
165
- position: absolute;
166
- top: 0;
167
- right: 0;
168
- width: 36px;
169
- height: 36px;
170
- text-align: center; }
171
-
172
- .ui-dialog-titlebar-close::before {
173
- font: normal 20px/36px 'dashicons';
174
- content: '\f158';
175
- vertical-align: middle;
176
- width: 36px;
177
- height: 36px; }
178
-
179
- .ui-dialog-titlebar-close:hover {
180
- background: transparent none;
181
- color: #2ea2cc; }
182
-
183
  /*# sourceMappingURL=style-modern-one.css.map */
39
  -moz-border-radius: 0;
40
  border-radius: 0; }
41
 
42
+ a.ws_edit_link {
43
  background: transparent;
44
  color: #A0A5AA;
45
  text-align: center;
46
  border-radius: 0; }
47
+ a.ws_edit_link::before {
48
  content: "\f140";
49
  font: normal 20px/1 dashicons;
50
  display: block; }
51
+ a.ws_edit_link:hover {
52
  color: #777; }
53
 
54
  .ws_edit_link.ws_edit_link_expanded::before {
147
  border-bottom: 1px solid #dfdfdf;
148
  height: auto;
149
  padding: 0; }
150
+ .ui-dialog-titlebar .ui-dialog-titlebar-close {
151
+ background: none;
152
+ border-style: none;
153
+ color: #666;
154
+ cursor: pointer;
155
+ padding: 0;
156
+ margin: 0;
157
+ position: absolute;
158
+ top: 0;
159
+ right: 0;
160
+ width: 36px;
161
+ height: 36px;
162
+ text-align: center; }
163
+ .ui-dialog-titlebar .ui-dialog-titlebar-close .ui-icon, .ui-dialog-titlebar .ui-dialog-titlebar-close .ui-button-text {
164
+ display: none; }
165
+ .ui-dialog-titlebar .ui-dialog-titlebar-close::before {
166
+ font: normal 20px/36px 'dashicons';
167
+ content: '\f158';
168
+ vertical-align: middle;
169
+ width: 36px;
170
+ height: 36px; }
171
+ .ui-dialog-titlebar .ui-dialog-titlebar-close:hover {
172
+ background: transparent none;
173
+ color: #2ea2cc; }
174
 
175
  .ui-dialog-title {
176
  color: #444444;
180
  padding: 0 36px 0 8px;
181
  display: block; }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  /*# sourceMappingURL=style-modern-one.css.map */
css/style-modern-one.scss CHANGED
@@ -62,6 +62,8 @@ $mainContainerWidth: $itemWidth + $columnPadding * 2;
62
  }
63
 
64
  .ws_flag_container {
 
 
65
  .ws_custom_actor_permissions_flag,
66
  .ws_custom_flag {
67
  display: none;
@@ -85,7 +87,7 @@ $mainContainerWidth: $itemWidth + $columnPadding * 2;
85
  border-radius: 0;
86
  }
87
 
88
- .ws_edit_link {
89
  background: transparent;
90
  color: #A0A5AA;
91
 
@@ -295,45 +297,50 @@ $mainContainerWidth: $itemWidth + $columnPadding * 2;
295
  border-bottom: 1px solid #dfdfdf;
296
  height: auto;
297
  padding: 0;
298
- }
299
 
300
- .ui-dialog-title {
301
- color: #444444;
302
- font-size: 18px;
303
- font-weight: 600;
304
- line-height: 36px;
305
 
306
- padding: 0 36px 0 8px;
307
- display: block;
308
- }
 
 
 
 
309
 
310
- .ui-dialog-titlebar-close {
311
- background: none;
312
- border-style: none;
313
 
314
- color: #666;
315
- cursor: pointer;
316
- padding: 0;
317
- position: absolute;
318
- top: 0;
319
- right: 0;
320
 
321
- width: 36px;
322
- height: 36px;
 
 
323
 
324
- text-align: center;
325
- }
 
326
 
327
- .ui-dialog-titlebar-close::before {
328
- font: normal 20px/36px 'dashicons';
329
- content: '\f158';
 
330
 
331
- vertical-align: middle;
332
- width: 36px;
333
- height: 36px;
 
334
  }
335
 
336
- .ui-dialog-titlebar-close:hover {
337
- background: transparent none;
338
- color: #2ea2cc;
 
 
 
 
 
339
  }
62
  }
63
 
64
  .ws_flag_container {
65
+ //Hide low-importance flags. It's debatable (knowing which roles have custom permissions is useful
66
+ //when debugging configuration issues), but lets leave them hidden for now.
67
  .ws_custom_actor_permissions_flag,
68
  .ws_custom_flag {
69
  display: none;
87
  border-radius: 0;
88
  }
89
 
90
+ a.ws_edit_link {
91
  background: transparent;
92
  color: #A0A5AA;
93
 
297
  border-bottom: 1px solid #dfdfdf;
298
  height: auto;
299
  padding: 0;
 
300
 
301
+ .ui-dialog-titlebar-close {
302
+ background: none;
303
+ border-style: none;
 
 
304
 
305
+ color: #666;
306
+ cursor: pointer;
307
+ padding: 0;
308
+ margin: 0;
309
+ position: absolute;
310
+ top: 0;
311
+ right: 0;
312
 
313
+ width: 36px;
314
+ height: 36px;
 
315
 
316
+ text-align: center;
 
 
 
 
 
317
 
318
+ .ui-icon, .ui-button-text {
319
+ display: none;
320
+ }
321
+ }
322
 
323
+ .ui-dialog-titlebar-close::before {
324
+ font: normal 20px/36px 'dashicons';
325
+ content: '\f158';
326
 
327
+ vertical-align: middle;
328
+ width: 36px;
329
+ height: 36px;
330
+ }
331
 
332
+ .ui-dialog-titlebar-close:hover {
333
+ background: transparent none;
334
+ color: #2ea2cc;
335
+ }
336
  }
337
 
338
+ .ui-dialog-title {
339
+ color: #444444;
340
+ font-size: 18px;
341
+ font-weight: 600;
342
+ line-height: 36px;
343
+
344
+ padding: 0 36px 0 8px;
345
+ display: block;
346
  }
includes/ajax-helper.php DELETED
@@ -1,178 +0,0 @@
1
- <?php
2
- class ameAjaxAction {
3
- protected $action = '';
4
- protected $handler;
5
-
6
- protected $requiredParameters = array();
7
- protected $defaultParameters = array();
8
- protected $checkAuthorization;
9
-
10
- public function __construct($uniqueActionName = null, $handler = null) {
11
- if (isset($uniqueActionName)) {
12
- $this->action = $uniqueActionName;
13
- }
14
- if (isset($handler)) {
15
- $this->handler = $handler;
16
- }
17
-
18
- if (empty($this->action)) {
19
- throw new LogicException(sprintf(
20
- 'AJAX action name is missing. You must either pass it to the %1$s constructor '
21
- . 'or give the %1$s::$action property a valid default value.',
22
- get_class($this)
23
- ));
24
- }
25
-
26
- $hookName = 'wp_ajax_' . $this->action;
27
- if (has_action($hookName)) {
28
- throw new RuntimeException(sprintf('The action name "%s" is already in use.', $this->action));
29
- }
30
- add_action('wp_ajax_' . $this->action, array($this, '_processRequest'));
31
- }
32
-
33
- /**
34
- * @access protected
35
- */
36
- public function _processRequest() {
37
- //Check nonce.
38
- if (!check_ajax_referer($this->action, false, false)) {
39
- $this->exitWithError(
40
- 'Access denied. Invalid nonce',
41
- 'invalid_nonce'
42
- );
43
- }
44
-
45
- $method = filter_input(INPUT_SERVER, 'REQUEST_METHOD');
46
-
47
- //Retrieve request parameters.
48
- $params = array();
49
- if ($method === 'GET') {
50
- $params = $_GET;
51
- } else if ($method === 'POST') {
52
- $params = $_POST;
53
- }
54
-
55
- //Remove magic quotes. WordPress applies them in wp-settings.php.
56
- if (did_action('sanitize_comment_cookies') && function_exists('wp_magic_quotes')) {
57
- $params = wp_unslash($params);
58
- }
59
-
60
- //Verify that all of the required parameters are present. Empty strings are not allowed.
61
- foreach($this->requiredParameters as $name) {
62
- if (!isset($params[$name]) || ($params[$name] === '')) {
63
- $this->exitWithError(
64
- sprintf('The required parameter "%s" is missing or empty.', $name),
65
- 'missing_required_parameter'
66
- );
67
- }
68
- }
69
-
70
- //Apply defaults.
71
- $params = array_merge($this->defaultParameters, $params);
72
-
73
- //Run custom authorization checks.
74
- $isAllowed = $this->isUserAuthorized($params);
75
- if ($isAllowed instanceof WP_Error) {
76
- $this->exitWithError($isAllowed->get_error_message(), $isAllowed->get_error_code());
77
- } else if (!$isAllowed) {
78
- $this->exitWithError(
79
- sprintf('You don\'t have permission to perform the "%s" action.', $this->action),
80
- 'access_denied'
81
- );
82
- }
83
-
84
- //Finally, perform the action.
85
- $response = $this->handleAction($params);
86
- if ($response instanceof WP_Error) {
87
- $this->exitWithError($response->get_error_message(), $response->get_error_code());
88
- }
89
- $this->outputResponse($response);
90
- exit;
91
- }
92
-
93
- protected function handleAction($params) {
94
- if (is_callable($this->handler)) {
95
- return call_user_func($this->handler, $params);
96
- } else {
97
- $this->exitWithError(
98
- sprintf(
99
- 'There is no request handler assigned to the "%1$s" action. '
100
- . 'Either override the %3$s method or set %2$s::$handler to a valid callback.',
101
- $this->action,
102
- __CLASS__,
103
- __METHOD__
104
- ),
105
- 'missing_ajax_handler'
106
- );
107
- return null;
108
- }
109
- }
110
-
111
- protected function exitWithError($message, $code = null) {
112
- if ( ($message === '') && !empty($code) ) {
113
- $message = $code;
114
- }
115
-
116
- $response = array(
117
- 'error' => array(
118
- 'message' => $message,
119
- 'code' => $code,
120
- )
121
- );
122
- $this->outputResponse($response);
123
- exit;
124
- }
125
-
126
- protected function outputResponse($response) {
127
- header('Content-Type: application/json');
128
- echo json_encode($response);
129
- }
130
-
131
- /**
132
- * Check if the current user is authorized to perform this action.
133
- *
134
- * @param array $params Request parameters.
135
- * @return bool|WP_Error
136
- */
137
- protected function isUserAuthorized($params) {
138
- if (isset($this->checkAuthorization)) {
139
- return call_user_func($this->checkAuthorization, $params);
140
- }
141
- return true;
142
- }
143
-
144
- //Just a bunch of fluent setters.
145
- //-------------------------------
146
-
147
- /**
148
- * @param string ...$param One or more parameter names.
149
- * @return $this
150
- */
151
- public function setRequiredParams($param) {
152
- $params = func_get_args();
153
- if (count($params) === 1 && is_array($params[0])) {
154
- $params = $params[0];
155
- }
156
-
157
- $this->requiredParameters = $params;
158
- return $this;
159
- }
160
-
161
- /**
162
- * @param callable $handler
163
- * @return $this
164
- */
165
- public function setHandler($handler) {
166
- $this->handler = $handler;
167
- return $this;
168
- }
169
-
170
- /**
171
- * @param callable $callback
172
- * @return $this
173
- */
174
- public function setAuthCallback($callback) {
175
- $this->checkAuthorization = $callback;
176
- return $this;
177
- }
178
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/auto-versioning.php CHANGED
@@ -54,14 +54,17 @@ class AutoVersioning {
54
  }
55
 
56
  private static function guess_filename_from_url($url) {
57
- $url_mappings = array(
58
- plugins_url() => WP_PLUGIN_DIR,
59
- plugins_url('', WPMU_PLUGIN_DIR . '/dummy') => WPMU_PLUGIN_DIR,
60
- get_stylesheet_directory_uri() => get_stylesheet_directory(),
61
- get_template_directory_uri() => get_template_directory(),
62
- content_url() => WP_CONTENT_DIR,
63
- site_url('/' . WPINC) => ABSPATH . WPINC,
64
- );
 
 
 
65
 
66
  $filename = null;
67
  foreach($url_mappings as $root_url => $directory) {
54
  }
55
 
56
  private static function guess_filename_from_url($url) {
57
+ static $url_mappings = null;
58
+ if ( $url_mappings === null ) {
59
+ $url_mappings = array(
60
+ plugins_url() => WP_PLUGIN_DIR,
61
+ plugins_url('', WPMU_PLUGIN_DIR . '/dummy') => WPMU_PLUGIN_DIR,
62
+ get_stylesheet_directory_uri() => get_stylesheet_directory(),
63
+ get_template_directory_uri() => get_template_directory(),
64
+ content_url() => WP_CONTENT_DIR,
65
+ site_url('/' . WPINC) => ABSPATH . WPINC,
66
+ );
67
+ }
68
 
69
  $filename = null;
70
  foreach($url_mappings as $root_url => $directory) {
includes/editor-page.php CHANGED
@@ -33,10 +33,30 @@ $hide_button_extra_tooltip = 'When "All" is selected, this will hide the menu fr
33
 
34
  //Output the "Upgrade to Pro" message
35
  if ( !apply_filters('admin_menu_editor_is_pro', false) ){
 
 
36
  ?>
37
  <script type="text/javascript">
38
  (function($){
39
- $('#screen-meta-links').append(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  '<div id="ws-pro-version-notice" class="custom-screen-meta-link-wrap">' +
41
  '<a href="http://adminmenueditor.com/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=top_upgrade_link&utm_campaign=Plugins" id="ws-pro-version-notice-link" class="show-settings custom-screen-meta-link" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
42
  '</div>'
33
 
34
  //Output the "Upgrade to Pro" message
35
  if ( !apply_filters('admin_menu_editor_is_pro', false) ){
36
+ //Pseudo-randomly decide whether to show the VAC link.
37
+ $is_vac_link_visible = (hexdec( substr(md5(get_site_url() . 'vc3'), -3) ) % 100) <= 20; //20% of sites will see it.
38
  ?>
39
  <script type="text/javascript">
40
  (function($){
41
+ var screenLinks = $('#screen-meta-links'),
42
+ showVacLink = (<?php echo $is_vac_link_visible ? 'true' : 'false' ?>);
43
+
44
+ if (showVacLink) {
45
+ screenLinks.append(
46
+ $('<div>', {
47
+ 'class' : 'custom-screen-meta-link-wrap',
48
+ 'id' : 'ws-visual-admin-customizer-ad'
49
+ }).append($('<a>', {
50
+ 'href' : 'https://wordpress.org/plugins/visual-admin-customizer/',
51
+ 'class' : 'show-settings custom-screen-meta-link',
52
+ 'title' : 'A free plugin for customizing the WordPress admin interface',
53
+ 'target': '_blank',
54
+ 'text' : 'Visual Admin Customizer'
55
+ }))
56
+ );
57
+ }
58
+
59
+ screenLinks.append(
60
  '<div id="ws-pro-version-notice" class="custom-screen-meta-link-wrap">' +
61
  '<a href="http://adminmenueditor.com/upgrade-to-pro/?utm_source=Admin%2BMenu%2BEditor%2Bfree&utm_medium=text_link&utm_content=top_upgrade_link&utm_campaign=Plugins" id="ws-pro-version-notice-link" class="show-settings custom-screen-meta-link" target="_blank" title="View Pro version details">Upgrade to Pro</a>' +
62
  '</div>'
includes/generate-menu-dashicons.php CHANGED
@@ -16,6 +16,8 @@ $dashiconsStylesheet = ABSPATH . WPINC . '/css/dashicons.css';
16
 
17
  $icons = array();
18
 
 
 
19
  $ignoreIcons = array('dashboard', 'editor-bold', 'editor-italic');
20
  $ignoreIcons = array_flip($ignoreIcons);
21
 
@@ -33,10 +35,10 @@ foreach($blocks as $block) {
33
 
34
  if ( preg_match('/\.dashicons-(?P<name>[\w\-]+):before/', $selector->getSelector(), $matches) ) {
35
  $name = $matches['name'];
 
36
 
37
- //We already have styles for icons that start with "admin-", and the arrow icons
38
- //aren't really suitable as menu icons.
39
- if ( preg_match('/^(admin|arrow)-/', $name) ) {
40
  break;
41
  }
42
 
@@ -57,12 +59,28 @@ foreach($blocks as $block) {
57
  }
58
  }
59
 
 
 
 
 
 
 
 
60
 
61
  break;
62
  }
63
  }
64
  }
65
 
 
 
 
 
 
 
 
 
 
66
  ?>
67
  <div class="wrap">
68
  <h2>Dashicons to Menu Icons</h2>
16
 
17
  $icons = array();
18
 
19
+ $allDashiconDefinitions = '';
20
+
21
  $ignoreIcons = array('dashboard', 'editor-bold', 'editor-italic');
22
  $ignoreIcons = array_flip($ignoreIcons);
23
 
35
 
36
  if ( preg_match('/\.dashicons-(?P<name>[\w\-]+):before/', $selector->getSelector(), $matches) ) {
37
  $name = $matches['name'];
38
+ $char = null;
39
 
40
+ //The arrow icons aren't really suitable as menu icons.
41
+ if ( preg_match('/^(arrow)-/', $name) ) {
 
42
  break;
43
  }
44
 
59
  }
60
  }
61
 
62
+ if (isset($char) && ($name !== 'before')) {
63
+ $allDashiconDefinitions .= sprintf(
64
+ '%s { content: "\%s" !important; }',
65
+ implode(', ', $selectors),
66
+ $char
67
+ ) . "\n";
68
+ }
69
 
70
  break;
71
  }
72
  }
73
  }
74
 
75
+ $dashiconComment = sprintf(
76
+ "/*\nThis file was automatically generated from /wp-includes/css/dashicons.css.\nLast update: %s\n*/",
77
+ date('c')
78
+ );
79
+ file_put_contents(
80
+ dirname(__FILE__) . '/../css/_dashicons.scss',
81
+ $dashiconComment . "\n" . $allDashiconDefinitions
82
+ );
83
+
84
  ?>
85
  <div class="wrap">
86
  <h2>Dashicons to Menu Icons</h2>
includes/menu-editor-core.php CHANGED
@@ -15,7 +15,7 @@ require $thisDirectory . '/ame-utils.php';
15
  require $thisDirectory . '/menu-item.php';
16
  require $thisDirectory . '/menu.php';
17
  require $thisDirectory . '/auto-versioning.php';
18
- require $thisDirectory . '/ajax-helper.php';
19
  require $thisDirectory . '/module.php';
20
 
21
  class WPMenuEditor extends MenuEd_ShadowPluginFramework {
@@ -141,6 +141,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
141
  //When to show submenu icons.
142
  'submenu_icons_enabled' => 'if_custom', //"never", "if_custom" or "always".
143
 
 
 
 
144
  //Menu editor UI colour scheme. "Classic" is the old blue/yellow scheme, and "wp-grey" is more WP-like.
145
  'ui_colour_scheme' => 'classic',
146
 
@@ -157,6 +160,15 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
157
 
158
  //Verbosity level of menu permission errors.
159
  'error_verbosity' => self::VERBOSITY_NORMAL,
 
 
 
 
 
 
 
 
 
160
  );
161
  $this->serialize_with_json = false; //(Don't) store the options in JSON format
162
 
@@ -241,6 +253,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
241
  //Tell first-time users where they can find the plugin settings page.
242
  add_action('all_admin_notices', array($this, 'display_plugin_menu_notice'));
243
 
 
 
 
244
  //Workaround for buggy plugins that unintentionally remove user roles.
245
  /** @see WPMenuEditor::get_user_roles */
246
  add_action('set_current_user', array($this, 'update_current_user_cache'), 1, 0); //Run before most plugins.
@@ -257,34 +272,6 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
257
  add_action('admin_menu_editor-display_header', array($this, 'display_settings_page_header'));
258
  add_action('admin_menu_editor-display_footer', array($this, 'display_settings_page_footer'));
259
 
260
- //Modules
261
- include dirname(__FILE__) . '/../modules/actor-selector/actor-selector.php';
262
- new ameActorSelector($this);
263
-
264
- include dirname(__FILE__) . '/../modules/plugin-visibility/plugin-visibility.php';
265
- new amePluginVisibility($this);
266
-
267
- $proModuleDirectory = AME_ROOT_DIR . '/extras/modules';
268
- if ( @is_dir($proModuleDirectory) ) {
269
- //The widget module requires PHP 5.3.
270
- if (
271
- version_compare(phpversion(), '5.3', '>=')
272
- && is_file($proModuleDirectory . '/dashboard-widget-editor/load.php')
273
- ) {
274
- require_once $proModuleDirectory . '/dashboard-widget-editor/load.php';
275
- new ameWidgetEditor($this);
276
- }
277
-
278
- if ( is_file($proModuleDirectory . '/super-users/super-users.php') ) {
279
- require $proModuleDirectory . '/super-users/super-users.php';
280
- new ameSuperUsers($this);
281
- }
282
- }
283
-
284
- //Set up the tabs for the menu editor page.
285
- $this->tabs = apply_filters('admin_menu_editor-tabs', array( 'editor' => 'Admin Menu', ));
286
- //The "Settings" tab is always last.
287
- $this->tabs['settings'] = 'Settings';
288
  }
289
 
290
  function init_finish() {
@@ -327,6 +314,20 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
327
  if ( $this->options['security_logging_enabled'] ) {
328
  add_action('admin_notices', array($this, 'display_security_log'));
329
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  }
331
 
332
  /**
@@ -423,6 +424,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
423
  $this->apply_woocommerce_compat_fix();
424
  //Compatibility fix for WordPress Mu Domain Mapping.
425
  $this->apply_wpmu_domain_mapping_fix();
 
 
426
  //As of WP 3.5, the "Links" menu is hidden by default.
427
  if ( !current_user_can('manage_links') ) {
428
  $this->remove_link_manager_menus();
@@ -495,6 +498,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
495
  add_action('in_admin_header', array($this, 'replace_wp_menu'), $ozh_adminmenu_priority - 1);
496
  add_action('in_admin_header', array($this, 'restore_wp_menu'), $ozh_adminmenu_priority + 1);
497
  }
 
 
498
  }
499
  }
500
 
@@ -517,6 +522,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
517
  list($menu, $submenu) = $this->filter_menu($menu, $submenu);
518
  $this->user_cap_cache_enabled = false;
519
 
 
520
  return $parent_file;
521
  }
522
 
@@ -644,16 +650,15 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
644
  return array($menu, $submenu);
645
  }
646
 
 
 
 
 
 
 
647
 
648
- /**
649
- * Add the JS required by the editor to the page header
650
- *
651
- * @return void
652
- */
653
- function enqueue_scripts() {
654
- //Optimization: Remove wp-emoji.js from the plugin page. wpEmoji makes DOM manipulation slow because
655
- //it tracks *all* DOM changes using MutationObserver.
656
- remove_action('admin_print_scripts', 'print_emoji_detection_script');
657
 
658
  //jQuery JSON plugin
659
  wp_register_auto_versioned_script('jquery-json', plugins_url('js/jquery.json.js', $this->plugin_file), array('jquery'));
@@ -666,20 +671,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
666
  //jQuery cookie plugin
667
  wp_register_auto_versioned_script('jquery-cookie', plugins_url('js/jquery.biscuit.js', $this->plugin_file), array('jquery'));
668
 
669
- //Lodash library
670
- wp_register_auto_versioned_script('ame-lodash', plugins_url('js/lodash.min.js', $this->plugin_file));
671
  //Knockout
672
  wp_register_auto_versioned_script('knockout', plugins_url('js/knockout.js', $this->plugin_file));
673
 
674
- //Move admin notices (e.g. "Settings saved") below editor tabs.
675
- //This is a separate script because it has to run after common.js which is loaded in the page footer.
676
- wp_enqueue_auto_versioned_script(
677
- 'ame-editor-tab-fix',
678
- plugins_url('js/editor-tab-fix.js', $this->plugin_file),
679
- array('jquery', 'common'),
680
- true
681
- );
682
-
683
  //Actor manager.
684
  wp_register_auto_versioned_script(
685
  'ame-actor-manager',
@@ -687,6 +681,43 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
687
  array('ame-lodash')
688
  );
689
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690
  //Modules
691
  wp_register_auto_versioned_script(
692
  'ame-access-editor',
@@ -696,6 +727,29 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
696
 
697
  //Let extras register their scripts.
698
  do_action('admin_menu_editor-register_scripts');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
 
700
  //Editor's scripts
701
  $editor_dependencies = array(
@@ -714,55 +768,24 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
714
  do_action('admin_menu_editor-enqueue_scripts-' . $this->current_tab);
715
 
716
  //Actors (roles and users) are used in the permissions UI, so we need to pass them along.
 
717
  $actors = array();
718
- $roles = array();
719
 
720
  $wp_roles = ameRoleUtils::get_roles();
721
  foreach($wp_roles->roles as $role_id => $role) {
722
  $actors['role:' . $role_id] = $role['name'];
723
- $role['capabilities'] = $this->castValuesToBool($role['capabilities']);
724
- $roles[$role_id] = $role;
725
  }
726
 
727
  if ( is_multisite() && is_super_admin() ) {
728
  $actors['special:super_admin'] = 'Super Admin';
729
  }
730
 
731
- //Known users.
732
- $users = array();
733
  $current_user = wp_get_current_user();
734
-
735
- $visible_users = isset($this->options['visible_users']) ? $this->options['visible_users'] : array();
736
- $logins_to_include = $visible_users;
737
-
738
- //Always include the current user.
739
- $logins_to_include[] = $current_user->get('user_login');
740
- $logins_to_include = array_unique($logins_to_include);
741
-
742
- //Load user details.
743
- foreach($logins_to_include as $login) {
744
- $user = get_user_by('login', $login);
745
- if ( !empty($user) ) {
746
- $users[$login] = $this->user_to_property_map($user);
747
- }
748
- }
749
-
750
- //Compatibility workaround: Get the real roles of the current user even if other plugins corrupt the list.
751
- $users[$current_user->get('user_login')]['roles'] = array_values($this->get_user_roles($current_user));
752
-
753
  $actors['user:' . $current_user->get('user_login')] = sprintf(
754
  'Current user (%s)',
755
  $current_user->get('user_login')
756
  );
757
 
758
- $actor_data = array(
759
- 'roles' => $roles,
760
- 'users' => $users,
761
- 'isMultisite' => is_multisite(),
762
- 'capPower' => $this->load_cap_power(),
763
- );
764
- wp_localize_script('ame-actor-manager', 'wsAmeActorData', $actor_data);
765
-
766
  //Add only certain scripts to the settings sub-section.
767
  if ( $this->is_settings_page() ) {
768
  wp_enqueue_script('jquery-qtip');
@@ -1003,6 +1026,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1003
  $previous_custom_menu = $this->load_custom_menu();
1004
  $this->update_wpml_strings($previous_custom_menu, $custom_menu);
1005
 
 
 
 
 
1006
  if ( $this->should_use_site_specific_menu() ) {
1007
  $site_specific_options = get_option($this->option_name);
1008
  if ( !is_array($site_specific_options) ) {
@@ -1077,6 +1104,41 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1077
  return current_user_can($capability);
1078
  }
1079
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
 
1081
  /**
1082
  * Apply the custom page title, if any.
@@ -1278,6 +1340,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1278
  //Now we have some items marked as missing, and some items in lookup arrays
1279
  //that are not marked as used. Lets remove the missing items from the tree.
1280
  $tree = ameMenu::remove_missing_items($tree);
 
1281
 
1282
  //Lets merge in the unused items.
1283
  $max_menu_position = !empty($positions_by_template) ? max($positions_by_template) : 100;
@@ -1640,6 +1703,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1640
  }
1641
  }
1642
 
 
 
 
 
1643
  //WPML support: Translate only custom titles. See further below.
1644
  $hasCustomMenuTitle = isset($item['menu_title']);
1645
 
@@ -1944,6 +2011,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1944
  $menu['granted_capabilities'] = $capFilter->clean_up($menu['granted_capabilities']);
1945
  }
1946
 
 
 
 
1947
  //Save the custom menu
1948
  $this->set_custom_menu($menu);
1949
 
@@ -2041,6 +2111,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2041
  }
2042
  }
2043
 
 
 
 
2044
  //Where to put new or unused menu items.
2045
  if ( !empty($this->post['unused_item_position']) ) {
2046
  $unused_item_position = strval($this->post['unused_item_position']);
@@ -2059,6 +2132,17 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2059
  }
2060
  }
2061
 
 
 
 
 
 
 
 
 
 
 
 
2062
  $this->save_options();
2063
  wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
2064
  }
@@ -2077,8 +2161,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2077
  );
2078
 
2079
  //Build a tree struct. for the default menu
2080
- $default_tree = ameMenu::wp2tree($this->default_wp_menu, $this->default_wp_submenu, $this->menu_url_blacklist);
2081
- $default_menu = ameMenu::load_array($default_tree);
2082
 
2083
  //Is there a custom menu?
2084
  if (!empty($this->merged_custom_menu)){
@@ -2126,6 +2209,34 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2126
  require dirname(__FILE__) . '/editor-page.php';
2127
  }
2128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2129
  /**
2130
  * Display the header of the "Menu Editor" page.
2131
  * This includes the page heading and tab list.
@@ -2181,6 +2292,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2181
  $settings_page_url = $this->get_settings_page_url();
2182
  /** @noinspection PhpUnusedLocalVariableInspection */
2183
  $editor_page_url = admin_url($this->settings_link);
 
 
2184
 
2185
  require dirname(__FILE__) . '/settings-page.php';
2186
  }
@@ -2926,6 +3039,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2926
  array(),
2927
  '20140630-3'
2928
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
2929
  }
2930
 
2931
  /**
@@ -3210,6 +3336,35 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
3210
  }
3211
  }
3212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3213
  /**
3214
  * As of WP 3.5, the Links Manager is hidden by default. It's only visible if the user has existing links
3215
  * or they choose to enable it by installing the Links Manager plugin.
@@ -3502,6 +3657,112 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
3502
  return $cap_power;
3503
  }
3504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3505
  } //class
3506
 
3507
 
@@ -3584,6 +3845,8 @@ class ameMenuTemplateBuilder {
3584
  }
3585
 
3586
  $templateId = ameMenuItem::template_id($item);
 
 
3587
  $this->templates[$templateId] = array(
3588
  'name' => $name,
3589
  'used' => false,
15
  require $thisDirectory . '/menu-item.php';
16
  require $thisDirectory . '/menu.php';
17
  require $thisDirectory . '/auto-versioning.php';
18
+ require $thisDirectory . '/../ajax-wrapper/AjaxWrapper.php';
19
  require $thisDirectory . '/module.php';
20
 
21
  class WPMenuEditor extends MenuEd_ShadowPluginFramework {
141
  //When to show submenu icons.
142
  'submenu_icons_enabled' => 'if_custom', //"never", "if_custom" or "always".
143
 
144
+ //Enable/disable CSS workaround that helps override menu icons set by other plugins.
145
+ 'force_custom_dashicons' => true,
146
+
147
  //Menu editor UI colour scheme. "Classic" is the old blue/yellow scheme, and "wp-grey" is more WP-like.
148
  'ui_colour_scheme' => 'classic',
149
 
160
 
161
  //Verbosity level of menu permission errors.
162
  'error_verbosity' => self::VERBOSITY_NORMAL,
163
+
164
+ //Enable/disable menu configuration compression. Enabling it makes the DB row much smaller,
165
+ //but adds decompression overhead to very admin page.
166
+ 'compress_custom_menu' => false,
167
+
168
+ //Which modules are active or inactive. Format: ['module-id' => true/false].
169
+ 'is_active_module' => array(
170
+ 'highlight-new-menus' => false,
171
+ ),
172
  );
173
  $this->serialize_with_json = false; //(Don't) store the options in JSON format
174
 
253
  //Tell first-time users where they can find the plugin settings page.
254
  add_action('all_admin_notices', array($this, 'display_plugin_menu_notice'));
255
 
256
+ //Reset plugin access if the only allowed user gets deleted or their ID changes.
257
+ add_action('wp_login', array($this, 'maybe_reset_plugin_access'), 10, 2);
258
+
259
  //Workaround for buggy plugins that unintentionally remove user roles.
260
  /** @see WPMenuEditor::get_user_roles */
261
  add_action('set_current_user', array($this, 'update_current_user_cache'), 1, 0); //Run before most plugins.
272
  add_action('admin_menu_editor-display_header', array($this, 'display_settings_page_header'));
273
  add_action('admin_menu_editor-display_footer', array($this, 'display_settings_page_footer'));
274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  }
276
 
277
  function init_finish() {
314
  if ( $this->options['security_logging_enabled'] ) {
315
  add_action('admin_notices', array($this, 'display_security_log'));
316
  }
317
+
318
+ //Modules
319
+ foreach($this->get_active_modules() as $module) {
320
+ /** @noinspection PhpIncludeInspection */
321
+ include ($module['path']);
322
+ if ( !empty($module['className']) ) {
323
+ new $module['className']($this);
324
+ }
325
+ }
326
+
327
+ //Set up the tabs for the menu editor page.
328
+ $this->tabs = apply_filters('admin_menu_editor-tabs', array( 'editor' => 'Admin Menu', ));
329
+ //The "Settings" tab is always last.
330
+ $this->tabs['settings'] = 'Settings';
331
  }
332
 
333
  /**
424
  $this->apply_woocommerce_compat_fix();
425
  //Compatibility fix for WordPress Mu Domain Mapping.
426
  $this->apply_wpmu_domain_mapping_fix();
427
+ //Compatibility fix for Divi Training.
428
+ $this->apply_divi_training_fix();
429
  //As of WP 3.5, the "Links" menu is hidden by default.
430
  if ( !current_user_can('manage_links') ) {
431
  $this->remove_link_manager_menus();
498
  add_action('in_admin_header', array($this, 'replace_wp_menu'), $ozh_adminmenu_priority - 1);
499
  add_action('in_admin_header', array($this, 'restore_wp_menu'), $ozh_adminmenu_priority + 1);
500
  }
501
+ } else {
502
+ do_action('admin_menu_editor-menu_replacement_skipped');
503
  }
504
  }
505
 
522
  list($menu, $submenu) = $this->filter_menu($menu, $submenu);
523
  $this->user_cap_cache_enabled = false;
524
 
525
+ do_action('admin_menu_editor-menu_replaced');
526
  return $parent_file;
527
  }
528
 
650
  return array($menu, $submenu);
651
  }
652
 
653
+ public function register_base_dependencies() {
654
+ static $done = false;
655
+ if ( $done ) {
656
+ return;
657
+ }
658
+ $done = true;
659
 
660
+ //Lodash library
661
+ wp_register_auto_versioned_script('ame-lodash', plugins_url('js/lodash.min.js', $this->plugin_file));
 
 
 
 
 
 
 
662
 
663
  //jQuery JSON plugin
664
  wp_register_auto_versioned_script('jquery-json', plugins_url('js/jquery.json.js', $this->plugin_file), array('jquery'));
671
  //jQuery cookie plugin
672
  wp_register_auto_versioned_script('jquery-cookie', plugins_url('js/jquery.biscuit.js', $this->plugin_file), array('jquery'));
673
 
 
 
674
  //Knockout
675
  wp_register_auto_versioned_script('knockout', plugins_url('js/knockout.js', $this->plugin_file));
676
 
 
 
 
 
 
 
 
 
 
677
  //Actor manager.
678
  wp_register_auto_versioned_script(
679
  'ame-actor-manager',
681
  array('ame-lodash')
682
  );
683
 
684
+ $roles = array();
685
+
686
+ $wp_roles = ameRoleUtils::get_roles();
687
+ foreach($wp_roles->roles as $role_id => $role) {
688
+ $role['capabilities'] = $this->castValuesToBool($role['capabilities']);
689
+ $roles[$role_id] = $role;
690
+ }
691
+
692
+ //Known users.
693
+ $users = array();
694
+ $current_user = wp_get_current_user();
695
+ $logins_to_include = apply_filters('admin_menu_editor-users_to_load', array());
696
+
697
+ //Always include the current user.
698
+ $logins_to_include[] = $current_user->get('user_login');
699
+ $logins_to_include = array_unique($logins_to_include);
700
+
701
+ //Load user details.
702
+ foreach($logins_to_include as $login) {
703
+ $user = get_user_by('login', $login);
704
+ if ( !empty($user) ) {
705
+ $users[$login] = $this->user_to_property_map($user);
706
+ }
707
+ }
708
+
709
+ //Compatibility workaround: Get the real roles of the current user even if other plugins corrupt the list.
710
+ $users[$current_user->get('user_login')]['roles'] = array_values($this->get_user_roles($current_user));
711
+
712
+ //TODO: Include currentUserLogin
713
+ $actor_data = array(
714
+ 'roles' => $roles,
715
+ 'users' => $users,
716
+ 'isMultisite' => is_multisite(),
717
+ 'capPower' => $this->load_cap_power(),
718
+ );
719
+ wp_localize_script('ame-actor-manager', 'wsAmeActorData', $actor_data);
720
+
721
  //Modules
722
  wp_register_auto_versioned_script(
723
  'ame-access-editor',
727
 
728
  //Let extras register their scripts.
729
  do_action('admin_menu_editor-register_scripts');
730
+ }
731
+
732
+
733
+ /**
734
+ * Add the JS required by the editor to the page header
735
+ *
736
+ * @return void
737
+ */
738
+ function enqueue_scripts() {
739
+ //Optimization: Remove wp-emoji.js from the plugin page. wpEmoji makes DOM manipulation slow because
740
+ //it tracks *all* DOM changes using MutationObserver.
741
+ remove_action('admin_print_scripts', 'print_emoji_detection_script');
742
+
743
+ $this->register_base_dependencies();
744
+
745
+ //Move admin notices (e.g. "Settings saved") below editor tabs.
746
+ //This is a separate script because it has to run after common.js which is loaded in the page footer.
747
+ wp_enqueue_auto_versioned_script(
748
+ 'ame-editor-tab-fix',
749
+ plugins_url('js/editor-tab-fix.js', $this->plugin_file),
750
+ array('jquery', 'common'),
751
+ true
752
+ );
753
 
754
  //Editor's scripts
755
  $editor_dependencies = array(
768
  do_action('admin_menu_editor-enqueue_scripts-' . $this->current_tab);
769
 
770
  //Actors (roles and users) are used in the permissions UI, so we need to pass them along.
771
+ //TODO: This is redundant. Consider using the actor manager or selector instead.
772
  $actors = array();
 
773
 
774
  $wp_roles = ameRoleUtils::get_roles();
775
  foreach($wp_roles->roles as $role_id => $role) {
776
  $actors['role:' . $role_id] = $role['name'];
 
 
777
  }
778
 
779
  if ( is_multisite() && is_super_admin() ) {
780
  $actors['special:super_admin'] = 'Super Admin';
781
  }
782
 
 
 
783
  $current_user = wp_get_current_user();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
  $actors['user:' . $current_user->get('user_login')] = sprintf(
785
  'Current user (%s)',
786
  $current_user->get('user_login')
787
  );
788
 
 
 
 
 
 
 
 
 
789
  //Add only certain scripts to the settings sub-section.
790
  if ( $this->is_settings_page() ) {
791
  wp_enqueue_script('jquery-qtip');
1026
  $previous_custom_menu = $this->load_custom_menu();
1027
  $this->update_wpml_strings($previous_custom_menu, $custom_menu);
1028
 
1029
+ if ( !empty($custom_menu) && $this->options['compress_custom_menu'] ) {
1030
+ $custom_menu = ameMenu::compress($custom_menu);
1031
+ }
1032
+
1033
  if ( $this->should_use_site_specific_menu() ) {
1034
  $site_specific_options = get_option($this->option_name);
1035
  if ( !is_array($site_specific_options) ) {
1104
  return current_user_can($capability);
1105
  }
1106
  }
1107
+
1108
+ /**
1109
+ * Reset plugin access if the only allowed user no longer exists.
1110
+ *
1111
+ * Some people use security plugins like iThemes Security to replace the default admin account
1112
+ * with a new one or change the user ID. This can be a problem when AME is configured to allow
1113
+ * only one user to edit the admin menu. Deleting that user ID makes the plugin inaccessible.
1114
+ * As a workaround, allow any admin if the configured user is missing.
1115
+ *
1116
+ * @internal
1117
+ * @param string $login
1118
+ * @param WP_User $current_user
1119
+ */
1120
+ public function maybe_reset_plugin_access(/** @noinspection PhpUnusedParameterInspection */ $login, $current_user) {
1121
+ if ( ($this->options['plugin_access'] !== 'specific_user') || !$current_user || !$current_user->exists() ) {
1122
+ return;
1123
+ }
1124
+
1125
+ //For performance, only run this check when an admin logs in.
1126
+ //Note that current_user_can() and friends don't work at this point in the login flow.
1127
+ $current_user_is_admin = is_multisite()
1128
+ ? is_super_admin($current_user->ID)
1129
+ : $current_user->has_cap('manage_options');
1130
+
1131
+ if ( !$current_user_is_admin ) {
1132
+ return;
1133
+ }
1134
+
1135
+ $allowed_user = get_user_by('id', $this->options['allowed_user_id']);
1136
+ if ( !$allowed_user || !$allowed_user->exists() ) {
1137
+ //The allowed user no longer exists. Allow any administrator to use the plugin.
1138
+ $this->options['plugin_access'] = 'manage_options';
1139
+ $this->save_options();
1140
+ }
1141
+ }
1142
 
1143
  /**
1144
  * Apply the custom page title, if any.
1340
  //Now we have some items marked as missing, and some items in lookup arrays
1341
  //that are not marked as used. Lets remove the missing items from the tree.
1342
  $tree = ameMenu::remove_missing_items($tree);
1343
+ //TODO: What would happen if we kept missing items?
1344
 
1345
  //Lets merge in the unused items.
1346
  $max_menu_position = !empty($positions_by_template) ? max($positions_by_template) : 100;
1703
  }
1704
  }
1705
 
1706
+ if ( $hasCustomIconUrl && (strpos(ameMenuItem::get($item, 'icon_url'), 'dashicons-') === 0) ) {
1707
+ $item['css_class'] = ameMenuItem::get($item, 'css_class', '') . ' ame-has-custom-dashicon';
1708
+ }
1709
+
1710
  //WPML support: Translate only custom titles. See further below.
1711
  $hasCustomMenuTitle = isset($item['menu_title']);
1712
 
2011
  $menu['granted_capabilities'] = $capFilter->clean_up($menu['granted_capabilities']);
2012
  }
2013
 
2014
+ //Remember if the user has changed any menu icons to different Dashicons.
2015
+ $menu['has_modified_dashicons'] = ameModifiedIconDetector::detect($menu);
2016
+
2017
  //Save the custom menu
2018
  $this->set_custom_menu($menu);
2019
 
2111
  }
2112
  }
2113
 
2114
+ //Work around icon CSS problems.
2115
+ $this->options['force_custom_dashicons'] = !empty($this->post['force_custom_dashicons']);
2116
+
2117
  //Where to put new or unused menu items.
2118
  if ( !empty($this->post['unused_item_position']) ) {
2119
  $unused_item_position = strval($this->post['unused_item_position']);
2132
  }
2133
  }
2134
 
2135
+ //Menu data compression.
2136
+ $this->options['compress_custom_menu'] = !empty($this->post['compress_custom_menu']);
2137
+
2138
+ //Active modules.
2139
+ $activeModules = isset($this->post['active_modules']) ? (array)$this->post['active_modules'] : array();
2140
+ $activeModules = array_fill_keys(array_map('strval', $activeModules), true);
2141
+ $this->options['is_active_module'] = array_merge(
2142
+ array_map('__return_false', $this->get_available_modules()),
2143
+ $activeModules
2144
+ );
2145
+
2146
  $this->save_options();
2147
  wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
2148
  }
2161
  );
2162
 
2163
  //Build a tree struct. for the default menu
2164
+ $default_menu = $this->get_default_menu();
 
2165
 
2166
  //Is there a custom menu?
2167
  if (!empty($this->merged_custom_menu)){
2209
  require dirname(__FILE__) . '/editor-page.php';
2210
  }
2211
 
2212
+ /**
2213
+ * Get the default admin menu configuration.
2214
+ *
2215
+ * @return array
2216
+ */
2217
+ private function get_default_menu() {
2218
+ $default_tree = ameMenu::wp2tree($this->default_wp_menu, $this->default_wp_submenu, $this->menu_url_blacklist);
2219
+ $default_menu = ameMenu::load_array($default_tree);
2220
+ return $default_menu;
2221
+ }
2222
+
2223
+ /**
2224
+ * Get the admin menu configuration that was used during this page load.
2225
+ *
2226
+ * @return array
2227
+ */
2228
+ public function get_active_admin_menu() {
2229
+ if ( !did_action('admin_menu') && !did_action('network_admin_menu') ) {
2230
+ throw new LogicException(__METHOD__ . ' was called too early. You must only call it after the admin menu is ready.');
2231
+ }
2232
+
2233
+ if (!empty($this->merged_custom_menu)){
2234
+ return $this->merged_custom_menu;
2235
+ } else {
2236
+ return $this->get_default_menu();
2237
+ }
2238
+ }
2239
+
2240
  /**
2241
  * Display the header of the "Menu Editor" page.
2242
  * This includes the page heading and tab list.
2292
  $settings_page_url = $this->get_settings_page_url();
2293
  /** @noinspection PhpUnusedLocalVariableInspection */
2294
  $editor_page_url = admin_url($this->settings_link);
2295
+ /** @noinspection PhpUnusedLocalVariableInspection */
2296
+ $db_option_name = $this->option_name;
2297
 
2298
  require dirname(__FILE__) . '/settings-page.php';
2299
  }
3039
  array(),
3040
  '20140630-3'
3041
  );
3042
+
3043
+ if ( $this->options['force_custom_dashicons'] ) {
3044
+ //Optimization: Only add the stylesheet if the menu actually has custom dashicons.
3045
+ $menu = $this->load_custom_menu();
3046
+ if ( $menu && !empty($menu['has_modified_dashicons']) ) {
3047
+ wp_enqueue_style(
3048
+ 'ame-force-dashicons',
3049
+ plugins_url('css/force-dashicons.css', $this->plugin_file),
3050
+ array(),
3051
+ '20170607'
3052
+ );
3053
+ }
3054
+ }
3055
  }
3056
 
3057
  /**
3336
  }
3337
  }
3338
 
3339
+ /**
3340
+ * Compatibility fix for Divi Training 1.3.5.
3341
+ *
3342
+ * The Divi Training plugin adds a whole lot of "hidden" submenu items to the Dashboard menu
3343
+ * and then removes them later. Lets get rid of them.
3344
+ */
3345
+ private function apply_divi_training_fix() {
3346
+ if ( !class_exists('Wm_Divi_Training_Admin', false) ) {
3347
+ return;
3348
+ }
3349
+ if ( !isset($this->default_wp_submenu, $this->default_wp_submenu['index.php']) ) {
3350
+ return;
3351
+ }
3352
+
3353
+ $items_to_remove = array();
3354
+ foreach($this->default_wp_submenu['index.php'] as $index => $menu) {
3355
+ //There's a lot of items, so we search for a common prefix instead of of including an explicit list.
3356
+ //
3357
+ if ( (strpos($menu[2], 'wm-divi-training-the-divi-') === 0) || ($menu[2] === 'wm-divi-training-updates')) {
3358
+ $items_to_remove[] = $index;
3359
+ }
3360
+ }
3361
+ foreach($items_to_remove as $index) {
3362
+ if ( isset($index, $this->default_wp_submenu['index.php'][$index]) ) {
3363
+ unset($this->default_wp_submenu['index.php'][$index]);
3364
+ }
3365
+ }
3366
+ }
3367
+
3368
  /**
3369
  * As of WP 3.5, the Links Manager is hidden by default. It's only visible if the user has existing links
3370
  * or they choose to enable it by installing the Links Manager plugin.
3657
  return $cap_power;
3658
  }
3659
 
3660
+ private function get_active_modules() {
3661
+ $modules = $this->get_available_modules();
3662
+
3663
+ $activeModules = array();
3664
+ foreach ($modules as $id => $module) {
3665
+ if ( $this->is_module_active($id, $module) ) {
3666
+ $activeModules[$id] = $module;
3667
+ }
3668
+ }
3669
+
3670
+ return $activeModules;
3671
+ }
3672
+
3673
+ public function get_available_modules() {
3674
+ $modules = array(
3675
+ 'actor-selector' => array(
3676
+ 'relativePath' => 'modules/actor-selector/actor-selector.php',
3677
+ 'className' => 'ameActorSelector',
3678
+ 'isAlwaysActive' => true,
3679
+ ),
3680
+ 'visible-users' => array(
3681
+ 'relativePath' => 'extras/modules/visible-users/visible-users.php',
3682
+ 'className' => 'ameVisibleUsers',
3683
+ 'isAlwaysActive' => true,
3684
+ ),
3685
+ 'metaboxes' => array(
3686
+ 'relativePath' => 'extras/modules/metaboxes/load.php',
3687
+ 'className' => 'ameMetaBoxEditor',
3688
+ 'requiredPhpVersion' => '5.3',
3689
+ 'title' => 'Meta Boxes',
3690
+ ),
3691
+ 'dashboard-widget-editor' => array(
3692
+ 'relativePath' => 'extras/modules/dashboard-widget-editor/load.php',
3693
+ 'className' => 'ameWidgetEditor',
3694
+ 'requiredPhpVersion' => '5.3',
3695
+ 'title' => 'Dashboard Widgets',
3696
+ ),
3697
+ 'plugin-visibility' => array(
3698
+ 'relativePath' => 'modules/plugin-visibility/plugin-visibility.php',
3699
+ 'className' => 'amePluginVisibility',
3700
+ 'title' => 'Plugins',
3701
+ ),
3702
+ 'super-users' => array(
3703
+ 'relativePath' => 'extras/modules/super-users/super-users.php',
3704
+ 'className' => 'ameSuperUsers',
3705
+ 'title' => 'Hidden Users',
3706
+ ),
3707
+ /*'admin-css' => array(
3708
+ 'relativePath' => 'modules/admin-css/admin-css.php',
3709
+ 'className' => 'ameAdminCss',
3710
+ 'title' => 'Admin CSS',
3711
+ ),*/
3712
+ 'hide-admin-menu' => array(
3713
+ 'relativePath' => 'extras/modules/hide-admin-menu/hide-admin-menu.php',
3714
+ 'className' => 'ameAdminMenuHider',
3715
+ 'title' => '"Show the admin menu" checkbox',
3716
+ ),
3717
+ 'hide-admin-bar' => array(
3718
+ 'relativePath' => 'extras/modules/hide-admin-bar/hide-admin-bar.php',
3719
+ 'className' => 'ameAdminBarHider',
3720
+ 'title' => '"Show the Toolbar" checkbox',
3721
+ ),
3722
+ 'highlight-new-menus' => array(
3723
+ 'relativePath' => 'modules/highlight-new-menus/highlight-new-menus.php',
3724
+ 'className' => 'ameMenuHighlighterWrapper',
3725
+ 'title' => 'Highlight new menu items',
3726
+ 'requiredPhpVersion' => '5.3',
3727
+ ),
3728
+ );
3729
+
3730
+ foreach($modules as &$module) {
3731
+ if (!empty($module['relativePath'])) {
3732
+ $module['path'] = AME_ROOT_DIR . '/' . $module['relativePath'];
3733
+ }
3734
+ }
3735
+ unset($module);
3736
+
3737
+ $modules = array_filter($modules, array($this, 'module_path_exists'));
3738
+
3739
+ return $modules;
3740
+ }
3741
+
3742
+ private function module_path_exists($module) {
3743
+ return !empty($module['path']) && file_exists($module['path']);
3744
+ }
3745
+
3746
+ public function is_module_compatible($module) {
3747
+ if ( !empty($module['requiredPhpVersion']) ) {
3748
+ return version_compare(phpversion(), $module['requiredPhpVersion'], '>=');
3749
+ }
3750
+ return true;
3751
+ }
3752
+
3753
+ public function is_module_active($id, $module) {
3754
+ if ( !$this->is_module_compatible($module) ) {
3755
+ return false;
3756
+ }
3757
+ if ( !empty($module['isAlwaysActive']) ) {
3758
+ return true;
3759
+ }
3760
+ if ( isset($this->options['is_active_module'][$id]) ) {
3761
+ return $this->options['is_active_module'][$id];
3762
+ }
3763
+ return true;
3764
+ }
3765
+
3766
  } //class
3767
 
3768
 
3845
  }
3846
 
3847
  $templateId = ameMenuItem::template_id($item);
3848
+ unset($item['template_id']);
3849
+
3850
  $this->templates[$templateId] = array(
3851
  'name' => $name,
3852
  'used' => false,
includes/menu-item.php CHANGED
@@ -354,6 +354,16 @@ abstract class ameMenuItem {
354
  unset($item['role_access']);
355
  }
356
 
 
 
 
 
 
 
 
 
 
 
357
  if ( isset($item['items']) ) {
358
  foreach($item['items'] as $index => $sub_item) {
359
  $item['items'][$index] = self::normalize($sub_item);
354
  unset($item['role_access']);
355
  }
356
 
357
+ //There's no need to store the default position if a custom position is set.
358
+ //The default position will not be used, and there's no option to reset the position to default.
359
+ if ( isset($item['position'], $item['defaults']['position']) && ($item['defaults']['position'] === $item['position'])) {
360
+ unset($item['defaults']['position']);
361
+ }
362
+ //The same goes for template ID.
363
+ if ( isset($item['template_id']) ) {
364
+ unset($item['defaults']['template_id']);
365
+ }
366
+
367
  if ( isset($item['items']) ) {
368
  foreach($item['items'] as $index => $sub_item) {
369
  $item['items'][$index] = self::normalize($sub_item);
includes/menu.php CHANGED
@@ -142,6 +142,11 @@ abstract class ameMenu {
142
  $menu['component_visibility'] = $visibility;
143
  }
144
 
 
 
 
 
 
145
  return $menu;
146
  }
147
 
@@ -412,16 +417,25 @@ abstract class ameMenu {
412
  }
413
 
414
  $common = $menu['format']['common'];
415
- $menu['tree'] = self::map_items(
416
- $menu['tree'],
417
- array(__CLASS__, 'decompress_item'),
418
- array($common)
419
- );
420
 
421
  unset($menu['format']['compressed'], $menu['format']['common']);
422
  return $menu;
423
  }
424
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  protected static function decompress_item($item, $common) {
426
  $item = array_merge($common['properties'], $item);
427
 
@@ -444,10 +458,11 @@ abstract class ameMenu {
444
  if ( $extra_params === null ) {
445
  $extra_params = array();
446
  }
 
447
 
448
  $result = array();
449
  foreach($items as $key => $item) {
450
- $args = array_merge(array($item), $extra_params);
451
  $item = call_user_func_array($callback, $args);
452
 
453
  if ( !empty($item['items']) ) {
@@ -457,6 +472,19 @@ abstract class ameMenu {
457
  }
458
  return $result;
459
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  }
461
 
462
  class ameGrantedCapabilityFilter {
@@ -498,6 +526,32 @@ class ameGrantedCapabilityFilter {
498
  }
499
  }
500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
 
502
  class InvalidMenuException extends Exception {}
503
 
142
  $menu['component_visibility'] = $visibility;
143
  }
144
 
145
+ //Copy the "modified icons" flag.
146
+ if ( isset($arr['has_modified_dashicons']) ) {
147
+ $menu['has_modified_dashicons'] = (bool)$arr['has_modified_dashicons'];
148
+ }
149
+
150
  return $menu;
151
  }
152
 
417
  }
418
 
419
  $common = $menu['format']['common'];
420
+ $menu['tree'] = self::decompress_list($menu['tree'], $common);
 
 
 
 
421
 
422
  unset($menu['format']['compressed'], $menu['format']['common']);
423
  return $menu;
424
  }
425
 
426
+ protected static function decompress_list($list, $common) {
427
+ //Optimization: Direct iteration is about 40% faster than map_items.
428
+ $result = array();
429
+ foreach($list as $key => $item) {
430
+ $item = self::decompress_item($item, $common);
431
+ if ( !empty($item['items']) ) {
432
+ $item['items'] = self::decompress_list($item['items'], $common);
433
+ }
434
+ $result[$key] = $item;
435
+ }
436
+ return $result;
437
+ }
438
+
439
  protected static function decompress_item($item, $common) {
440
  $item = array_merge($common['properties'], $item);
441
 
458
  if ( $extra_params === null ) {
459
  $extra_params = array();
460
  }
461
+ $args = array_merge(array(null), $extra_params);
462
 
463
  $result = array();
464
  foreach($items as $key => $item) {
465
+ $args[0] = $item;
466
  $item = call_user_func_array($callback, $args);
467
 
468
  if ( !empty($item['items']) ) {
472
  }
473
  return $result;
474
  }
475
+
476
+ /**
477
+ * @param array $items
478
+ * @param callable $callback
479
+ */
480
+ public static function for_each($items, $callback) {
481
+ foreach($items as $key => $item) {
482
+ call_user_func($callback, $item);
483
+ if ( !empty($item['items']) ) {
484
+ self::for_each($item['items'], $callback);
485
+ }
486
+ }
487
+ }
488
  }
489
 
490
  class ameGrantedCapabilityFilter {
526
  }
527
  }
528
 
529
+ /**
530
+ * This could just be a closure, but we want to support PHP 5.2.
531
+ */
532
+ class ameModifiedIconDetector {
533
+ private $result = false;
534
+
535
+ public static function detect($menu) {
536
+ $detector = new self();
537
+ ameMenu::for_each($menu['tree'], array($detector, 'checkItem'));
538
+ return $detector->getResult();
539
+ }
540
+
541
+ public function checkItem($item) {
542
+ $this->result = $this->result || $this->hasModifiedDashicon($item);
543
+ }
544
+
545
+ private function hasModifiedDashicon($item) {
546
+ return !ameMenuItem::is_default($item, 'icon_url')
547
+ && (strpos(ameMenuItem::get($item, 'icon_url'), 'dashicons-') === 0);
548
+ }
549
+
550
+ private function getResult() {
551
+ return $this->result;
552
+ }
553
+ }
554
+
555
 
556
  class InvalidMenuException extends Exception {}
557
 
includes/reflection-callable.php CHANGED
@@ -40,13 +40,27 @@ class AmeReflectionCallable {
40
  $callback = array($callback, '__invoke');
41
  }
42
 
43
- if (is_object($callback[0])) {
44
  $reflectionObject = new ReflectionObject($callback[0]);
45
  } else {
46
  $reflectionObject = new ReflectionClass($callback[0]);
47
  }
48
 
49
- return $reflectionObject->getMethod($callback[1]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
 
52
  /**
40
  $callback = array($callback, '__invoke');
41
  }
42
 
43
+ if ( is_object($callback[0]) ) {
44
  $reflectionObject = new ReflectionObject($callback[0]);
45
  } else {
46
  $reflectionObject = new ReflectionClass($callback[0]);
47
  }
48
 
49
+ $methodName = $callback[1];
50
+ if ( !$reflectionObject->hasMethod($methodName) ) {
51
+ //The callback appears to use magic methods.
52
+ if ( is_string($callback[0]) && $reflectionObject->hasMethod('__callStatic') ) {
53
+ $methodName = '__callStatic';
54
+ } else if (is_object($callback[0]) && $reflectionObject->hasMethod('__call')) {
55
+ $methodName = '__call';
56
+ } else {
57
+ //Probably an invalid callback. It could be a relative static method call,
58
+ //but we don't support those at the moment.
59
+ //See http://php.net/manual/en/language.types.callable.php
60
+ }
61
+ }
62
+
63
+ return $reflectionObject->getMethod($methodName);
64
  }
65
 
66
  /**
includes/settings-page.php CHANGED
@@ -6,6 +6,7 @@
6
  * @var array $settings Plugin settings.
7
  * @var string $editor_page_url A fully qualified URL of the admin menu editor page.
8
  * @var string $settings_page_url
 
9
  */
10
 
11
  $currentUser = wp_get_current_user();
@@ -116,6 +117,62 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
116
  </td>
117
  </tr>
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  <tr>
120
  <th scope="row">Interface</th>
121
  <td>
@@ -298,6 +355,7 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
298
  <tr>
299
  <th scope="row">Debugging</th>
300
  <td>
 
301
  <label>
302
  <input type="checkbox" name="security_logging_enabled"
303
  <?php checked($settings['security_logging_enabled']); ?>>
@@ -310,6 +368,53 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
310
  Note: It's not recommended to use this option on a live site as
311
  it can reveal information about your menu configuration.
312
  </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  </td>
314
  </tr>
315
  </tbody>
6
  * @var array $settings Plugin settings.
7
  * @var string $editor_page_url A fully qualified URL of the admin menu editor page.
8
  * @var string $settings_page_url
9
+ * @var string $db_option_name
10
  */
11
 
12
  $currentUser = wp_get_current_user();
117
  </td>
118
  </tr>
119
 
120
+ <tr>
121
+ <th scope="row">
122
+ Modules
123
+ <a class="ws_tooltip_trigger"
124
+ title="Modules are plugin features that can be turned on or off.
125
+ &lt;br&gt;
126
+ Turning off unused features will slightly increase performance and may help with certain compatibility issues.
127
+ ">
128
+ <div class="dashicons dashicons-info"></div>
129
+ </a>
130
+ </th>
131
+ <td>
132
+ <fieldset>
133
+ <?php
134
+ global $wp_menu_editor;
135
+ foreach ($wp_menu_editor->get_available_modules() as $id => $module) {
136
+ if ( !empty($module['isAlwaysActive']) ) {
137
+ continue;
138
+ }
139
+
140
+ $isCompatible = $wp_menu_editor->is_module_compatible($module);
141
+ $compatibilityNote = '';
142
+ if ( !$isCompatible && !empty($module['requiredPhpVersion']) ) {
143
+ if ( version_compare(phpversion(), $module['requiredPhpVersion'], '<') ) {
144
+ $compatibilityNote = sprintf(
145
+ 'Required PHP version: %1$s or later. Installed PHP version: %2$s',
146
+ htmlentities($module['requiredPhpVersion']),
147
+ htmlentities(phpversion())
148
+ );
149
+ }
150
+ }
151
+
152
+ echo '<p>';
153
+ /** @noinspection HtmlUnknownAttribute */
154
+ printf(
155
+ '<label>
156
+ <input type="checkbox" name="active_modules[]" value="%1$s" %2$s %3$s>
157
+ %4$s
158
+ </label>',
159
+ esc_attr($id),
160
+ $wp_menu_editor->is_module_active($id, $module) ? 'checked="checked"' : '',
161
+ $isCompatible ? '' : 'disabled="disabled"',
162
+ !empty($module['title']) ? $module['title'] : htmlentities($id)
163
+ );
164
+
165
+ if ( !empty($compatibilityNote) ) {
166
+ printf('<br><span class="description">%s</span>', $compatibilityNote);
167
+ }
168
+
169
+ echo '</p>';
170
+ }
171
+ ?>
172
+ </fieldset>
173
+ </td>
174
+ </tr>
175
+
176
  <tr>
177
  <th scope="row">Interface</th>
178
  <td>
355
  <tr>
356
  <th scope="row">Debugging</th>
357
  <td>
358
+ <p>
359
  <label>
360
  <input type="checkbox" name="security_logging_enabled"
361
  <?php checked($settings['security_logging_enabled']); ?>>
368
  Note: It's not recommended to use this option on a live site as
369
  it can reveal information about your menu configuration.
370
  </span>
371
+ </p>
372
+
373
+ <p>
374
+ <label>
375
+ <input type="checkbox" name="force_custom_dashicons"
376
+ <?php checked($settings['force_custom_dashicons']); ?>>
377
+ Attempt to override menu icon CSS that was added by other plugins
378
+ </label>
379
+ </p>
380
+
381
+ <p>
382
+ <label>
383
+ <input type="checkbox" name="compress_custom_menu"
384
+ <?php checked($settings['compress_custom_menu']); ?>>
385
+ Compress menu configuration data that's stored in the database
386
+ </label>
387
+ <br><span class="description">
388
+ Significantly reduces the size of
389
+ the <code><?php echo esc_html($db_option_name); ?></code> DB option,
390
+ but adds decompression overhead to every page.
391
+ </span>
392
+ </p>
393
+ </td>
394
+ </tr>
395
+
396
+ <tr>
397
+ <th scope="row">Server info</th>
398
+ <td>
399
+ <figure>
400
+ <figcaption>PHP error log:</figcaption>
401
+
402
+ <code><?php
403
+ echo esc_html(ini_get('error_log'));
404
+ ?></code>
405
+ </figure>
406
+
407
+ <figure>
408
+ <figcaption>PHP memory usage:</figcaption>
409
+
410
+ <?php
411
+ printf(
412
+ '%.2f MiB of %s',
413
+ memory_get_peak_usage() / (1024 * 1024),
414
+ esc_html(ini_get('memory_limit'))
415
+ );
416
+ ?>
417
+ </figure>
418
  </td>
419
  </tr>
420
  </tbody>
js/actor-manager.js CHANGED
@@ -1,10 +1,15 @@
1
  /// <reference path="lodash-3.10.d.ts" />
2
  /// <reference path="common.d.ts" />
3
- var __extends = (this && this.__extends) || function (d, b) {
4
- for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
5
- function __() { this.constructor = d; }
6
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
7
- };
 
 
 
 
 
8
  var AmeBaseActor = (function () {
9
  function AmeBaseActor(id, displayName, capabilities) {
10
  this.displayName = '[Error: No displayName set]';
@@ -54,9 +59,10 @@ var AmeBaseActor = (function () {
54
  var AmeRole = (function (_super) {
55
  __extends(AmeRole, _super);
56
  function AmeRole(roleId, displayName, capabilities) {
57
- _super.call(this, 'role:' + roleId, displayName, capabilities);
58
- this.actorTypeSpecificity = 1;
59
- this.name = roleId;
 
60
  }
61
  AmeRole.prototype.hasOwnCap = function (capability) {
62
  //In WordPress, a role name is also a capability name. Users that have the role "foo" always
@@ -73,21 +79,22 @@ var AmeUser = (function (_super) {
73
  __extends(AmeUser, _super);
74
  function AmeUser(userLogin, displayName, capabilities, roles, isSuperAdmin, userId) {
75
  if (isSuperAdmin === void 0) { isSuperAdmin = false; }
76
- _super.call(this, 'user:' + userLogin, displayName, capabilities);
77
- this.userId = 0;
78
- this.isSuperAdmin = false;
79
- this.avatarHTML = '';
80
- this.actorTypeSpecificity = 10;
81
- this.userLogin = userLogin;
82
- this.roles = roles;
83
- this.isSuperAdmin = isSuperAdmin;
84
- this.userId = userId || 0;
85
- if (this.isSuperAdmin) {
86
- this.groupActors.push(AmeSuperAdmin.permanentActorId);
87
  }
88
- for (var i = 0; i < this.roles.length; i++) {
89
- this.groupActors.push('role:' + this.roles[i]);
90
  }
 
91
  }
92
  AmeUser.createFromProperties = function (properties) {
93
  var user = new AmeUser(properties.user_login, properties.display_name, properties.capabilities, properties.roles, properties.is_super_admin, properties.hasOwnProperty('id') ? properties.id : null);
@@ -101,20 +108,21 @@ var AmeUser = (function (_super) {
101
  var AmeSuperAdmin = (function (_super) {
102
  __extends(AmeSuperAdmin, _super);
103
  function AmeSuperAdmin() {
104
- _super.call(this, AmeSuperAdmin.permanentActorId, 'Super Admin', {});
105
- this.actorTypeSpecificity = 2;
 
106
  }
107
  AmeSuperAdmin.prototype.hasOwnCap = function (capability) {
108
  //The Super Admin has all possible capabilities except the special "do_not_allow" flag.
109
  return (capability !== 'do_not_allow');
110
  };
111
- AmeSuperAdmin.permanentActorId = 'special:super_admin';
112
  return AmeSuperAdmin;
113
  }(AmeBaseActor));
 
114
  var AmeActorManager = (function () {
115
  function AmeActorManager(roles, users, isMultisite) {
116
- var _this = this;
117
  if (isMultisite === void 0) { isMultisite = false; }
 
118
  this.roles = {};
119
  this.users = {};
120
  this.grantedCapabilities = {};
@@ -187,11 +195,12 @@ var AmeActorManager = (function () {
187
  return true;
188
  }
189
  capability = AmeActorManager.mapMetaCap(capability);
 
190
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
191
  //Step #2: Check granted capabilities. Default on, but can be skipped.
192
  if (contextList) {
193
  //Check for explicit settings first.
194
- var result = null, actorValue, len = contextList.length;
195
  for (var i = 0; i < len; i++) {
196
  if (contextList[i].hasOwnProperty(actorId)) {
197
  actorValue = contextList[i][actorId];
@@ -363,7 +372,8 @@ var AmeActorManager = (function () {
363
  var rolesByPower = _.values(this.getRoles()).sort(function (a, b) {
364
  var aCaps = capsByPower(a), bCaps = capsByPower(b);
365
  //Prioritise roles with the highest number of the most powerful capabilities.
366
- for (var i = 0, limit = Math.min(aCaps.length, bCaps.length); i < limit; i++) {
 
367
  var delta_1 = bCaps[i].power - aCaps[i].power;
368
  if (delta_1 !== 0) {
369
  return delta_1;
@@ -430,9 +440,9 @@ var AmeActorManager = (function () {
430
  AmeActorManager.prototype.getSuggestedCapabilities = function () {
431
  return this.suggestedCapabilities;
432
  };
433
- AmeActorManager._ = wsAmeLodash;
434
  return AmeActorManager;
435
  }());
 
436
  if (typeof wsAmeActorData !== 'undefined') {
437
  AmeActors = new AmeActorManager(wsAmeActorData.roles, wsAmeActorData.users, wsAmeActorData.isMultisite);
438
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
1
  /// <reference path="lodash-3.10.d.ts" />
2
  /// <reference path="common.d.ts" />
3
+ var __extends = (this && this.__extends) || (function () {
4
+ var extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
7
+ return function (d, b) {
8
+ extendStatics(d, b);
9
+ function __() { this.constructor = d; }
10
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
11
+ };
12
+ })();
13
  var AmeBaseActor = (function () {
14
  function AmeBaseActor(id, displayName, capabilities) {
15
  this.displayName = '[Error: No displayName set]';
59
  var AmeRole = (function (_super) {
60
  __extends(AmeRole, _super);
61
  function AmeRole(roleId, displayName, capabilities) {
62
+ var _this = _super.call(this, 'role:' + roleId, displayName, capabilities) || this;
63
+ _this.actorTypeSpecificity = 1;
64
+ _this.name = roleId;
65
+ return _this;
66
  }
67
  AmeRole.prototype.hasOwnCap = function (capability) {
68
  //In WordPress, a role name is also a capability name. Users that have the role "foo" always
79
  __extends(AmeUser, _super);
80
  function AmeUser(userLogin, displayName, capabilities, roles, isSuperAdmin, userId) {
81
  if (isSuperAdmin === void 0) { isSuperAdmin = false; }
82
+ var _this = _super.call(this, 'user:' + userLogin, displayName, capabilities) || this;
83
+ _this.userId = 0;
84
+ _this.isSuperAdmin = false;
85
+ _this.avatarHTML = '';
86
+ _this.actorTypeSpecificity = 10;
87
+ _this.userLogin = userLogin;
88
+ _this.roles = roles;
89
+ _this.isSuperAdmin = isSuperAdmin;
90
+ _this.userId = userId || 0;
91
+ if (_this.isSuperAdmin) {
92
+ _this.groupActors.push(AmeSuperAdmin.permanentActorId);
93
  }
94
+ for (var i = 0; i < _this.roles.length; i++) {
95
+ _this.groupActors.push('role:' + _this.roles[i]);
96
  }
97
+ return _this;
98
  }
99
  AmeUser.createFromProperties = function (properties) {
100
  var user = new AmeUser(properties.user_login, properties.display_name, properties.capabilities, properties.roles, properties.is_super_admin, properties.hasOwnProperty('id') ? properties.id : null);
108
  var AmeSuperAdmin = (function (_super) {
109
  __extends(AmeSuperAdmin, _super);
110
  function AmeSuperAdmin() {
111
+ var _this = _super.call(this, AmeSuperAdmin.permanentActorId, 'Super Admin', {}) || this;
112
+ _this.actorTypeSpecificity = 2;
113
+ return _this;
114
  }
115
  AmeSuperAdmin.prototype.hasOwnCap = function (capability) {
116
  //The Super Admin has all possible capabilities except the special "do_not_allow" flag.
117
  return (capability !== 'do_not_allow');
118
  };
 
119
  return AmeSuperAdmin;
120
  }(AmeBaseActor));
121
+ AmeSuperAdmin.permanentActorId = 'special:super_admin';
122
  var AmeActorManager = (function () {
123
  function AmeActorManager(roles, users, isMultisite) {
 
124
  if (isMultisite === void 0) { isMultisite = false; }
125
+ var _this = this;
126
  this.roles = {};
127
  this.users = {};
128
  this.grantedCapabilities = {};
195
  return true;
196
  }
197
  capability = AmeActorManager.mapMetaCap(capability);
198
+ var result = null;
199
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
200
  //Step #2: Check granted capabilities. Default on, but can be skipped.
201
  if (contextList) {
202
  //Check for explicit settings first.
203
+ var actorValue = void 0, len = contextList.length;
204
  for (var i = 0; i < len; i++) {
205
  if (contextList[i].hasOwnProperty(actorId)) {
206
  actorValue = contextList[i][actorId];
372
  var rolesByPower = _.values(this.getRoles()).sort(function (a, b) {
373
  var aCaps = capsByPower(a), bCaps = capsByPower(b);
374
  //Prioritise roles with the highest number of the most powerful capabilities.
375
+ var i = 0, limit = Math.min(aCaps.length, bCaps.length);
376
+ for (; i < limit; i++) {
377
  var delta_1 = bCaps[i].power - aCaps[i].power;
378
  if (delta_1 !== 0) {
379
  return delta_1;
440
  AmeActorManager.prototype.getSuggestedCapabilities = function () {
441
  return this.suggestedCapabilities;
442
  };
 
443
  return AmeActorManager;
444
  }());
445
+ AmeActorManager._ = wsAmeLodash;
446
  if (typeof wsAmeActorData !== 'undefined') {
447
  AmeActors = new AmeActorManager(wsAmeActorData.roles, wsAmeActorData.users, wsAmeActorData.isMultisite);
448
  if (typeof wsAmeActorData['capPower'] !== 'undefined') {
js/actor-manager.ts CHANGED
@@ -1,9 +1,9 @@
1
  /// <reference path="lodash-3.10.d.ts" />
2
  /// <reference path="common.d.ts" />
3
 
4
- declare var wsAmeActorData: any;
5
  declare var wsAmeLodash: _.LoDashStatic;
6
- declare var AmeActors: AmeActorManager;
7
 
8
  interface CapabilityMap {
9
  [capabilityName: string] : boolean;
@@ -41,7 +41,7 @@ abstract class AmeBaseActor {
41
  }
42
 
43
  static getActorSpecificity(actorId: string) {
44
- var actorType = actorId.substring(0, actorId.indexOf(':')),
45
  specificity = 0;
46
  switch (actorType) {
47
  case 'role':
@@ -122,7 +122,7 @@ class AmeUser extends AmeBaseActor {
122
  if (this.isSuperAdmin) {
123
  this.groupActors.push(AmeSuperAdmin.permanentActorId);
124
  }
125
- for (var i = 0; i < this.roles.length; i++) {
126
  this.groupActors.push('role:' + this.roles[i]);
127
  }
128
  }
@@ -186,12 +186,12 @@ class AmeActorManager {
186
  this.isMultisite = !!isMultisite;
187
 
188
  AmeActorManager._.forEach(roles, (roleDetails, id) => {
189
- var role = new AmeRole(id, roleDetails.name, roleDetails.capabilities);
190
  this.roles[role.name] = role;
191
  });
192
 
193
  AmeActorManager._.forEach(users, (userDetails: AmeUserPropertyMap) => {
194
- var user = AmeUser.createFromProperties(userDetails);
195
  this.users[user.userLogin] = user;
196
  });
197
 
@@ -219,7 +219,7 @@ class AmeActorManager {
219
  return this.superAdmin;
220
  }
221
 
222
- var separator = actorId.indexOf(':'),
223
  actorType = actorId.substring(0, separator),
224
  actorKey = actorId.substring(separator + 1);
225
 
@@ -266,13 +266,14 @@ class AmeActorManager {
266
  }
267
 
268
  capability = AmeActorManager.mapMetaCap(capability);
 
269
 
270
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
271
  //Step #2: Check granted capabilities. Default on, but can be skipped.
272
  if (contextList) {
273
  //Check for explicit settings first.
274
- var result = null, actorValue, len = contextList.length;
275
- for (var i = 0; i < len; i++) {
276
  if (contextList[i].hasOwnProperty(actorId)) {
277
  actorValue = contextList[i][actorId];
278
  if (typeof actorValue === 'boolean') {
@@ -289,7 +290,7 @@ class AmeActorManager {
289
  }
290
 
291
  //Step #3: Check owned/default capabilities. Always checked.
292
- var actor = this.getActor(actorId),
293
  hasOwnCap = actor.hasOwnCap(capability);
294
  if (hasOwnCap !== null) {
295
  return hasOwnCap;
@@ -305,7 +306,7 @@ class AmeActorManager {
305
 
306
  //Check if any of the user's roles have the capability.
307
  result = false;
308
- for (var index = 0; index < actor.roles.length; index++) {
309
  result = result || this.actorHasCap('role:' + actor.roles[index], capability, contextList);
310
  }
311
  return result;
@@ -390,7 +391,7 @@ class AmeActorManager {
390
  ) {
391
  capability = AmeActorManager.mapMetaCap(capability);
392
 
393
- var grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
394
  AmeActorManager._.set(context, [actor, capability], grant);
395
  }
396
 
@@ -413,13 +414,13 @@ class AmeActorManager {
413
  * the direct grant is redundant. We can remove it. Jane will still have "edit_posts" because she's an editor.
414
  */
415
  pruneGrantedUserCapabilities(): AmeGrantedCapabilityMap {
416
- var _ = AmeActorManager._,
417
  pruned = _.cloneDeep(this.grantedCapabilities),
418
  context = [pruned];
419
 
420
- var actorKeys = _(pruned).keys().filter((actorId) => {
421
  //Skip users that are not loaded.
422
- var actor = this.getActor(actorId);
423
  if (actor === null) {
424
  return false;
425
  }
@@ -428,10 +429,10 @@ class AmeActorManager {
428
 
429
  _.forEach(actorKeys, (actor) => {
430
  _.forEach(_.keys(pruned[actor]), (capability) => {
431
- var grant = pruned[actor][capability];
432
  delete pruned[actor][capability];
433
 
434
- var hasCap = _.isArray(grant) ? grant[0] : grant,
435
  hasCapWhenPruned = this.actorHasCap(actor, capability, context);
436
 
437
  if (hasCap !== hasCapWhenPruned) {
@@ -454,7 +455,7 @@ class AmeActorManager {
454
  * @return {Number}
455
  */
456
  static compareActorSpecificity(actor1: string, actor2: string): Number {
457
- var delta = AmeBaseActor.getActorSpecificity(actor1) - AmeBaseActor.getActorSpecificity(actor2);
458
  if (delta !== 0) {
459
  delta = (delta > 0) ? 1 : -1;
460
  }
@@ -484,7 +485,8 @@ class AmeActorManager {
484
  bCaps = capsByPower(b);
485
 
486
  //Prioritise roles with the highest number of the most powerful capabilities.
487
- for (var i = 0, limit = Math.min(aCaps.length, bCaps.length); i < limit; i++) {
 
488
  let delta = bCaps[i].power - aCaps[i].power;
489
  if (delta !== 0) {
490
  return delta;
1
  /// <reference path="lodash-3.10.d.ts" />
2
  /// <reference path="common.d.ts" />
3
 
4
+ declare let wsAmeActorData: any;
5
  declare var wsAmeLodash: _.LoDashStatic;
6
+ declare let AmeActors: AmeActorManager;
7
 
8
  interface CapabilityMap {
9
  [capabilityName: string] : boolean;
41
  }
42
 
43
  static getActorSpecificity(actorId: string) {
44
+ let actorType = actorId.substring(0, actorId.indexOf(':')),
45
  specificity = 0;
46
  switch (actorType) {
47
  case 'role':
122
  if (this.isSuperAdmin) {
123
  this.groupActors.push(AmeSuperAdmin.permanentActorId);
124
  }
125
+ for (let i = 0; i < this.roles.length; i++) {
126
  this.groupActors.push('role:' + this.roles[i]);
127
  }
128
  }
186
  this.isMultisite = !!isMultisite;
187
 
188
  AmeActorManager._.forEach(roles, (roleDetails, id) => {
189
+ const role = new AmeRole(id, roleDetails.name, roleDetails.capabilities);
190
  this.roles[role.name] = role;
191
  });
192
 
193
  AmeActorManager._.forEach(users, (userDetails: AmeUserPropertyMap) => {
194
+ const user = AmeUser.createFromProperties(userDetails);
195
  this.users[user.userLogin] = user;
196
  });
197
 
219
  return this.superAdmin;
220
  }
221
 
222
+ const separator = actorId.indexOf(':'),
223
  actorType = actorId.substring(0, separator),
224
  actorKey = actorId.substring(separator + 1);
225
 
266
  }
267
 
268
  capability = AmeActorManager.mapMetaCap(capability);
269
+ let result = null;
270
 
271
  //Step #1: Check temporary context - unsaved caps, etc. Optional.
272
  //Step #2: Check granted capabilities. Default on, but can be skipped.
273
  if (contextList) {
274
  //Check for explicit settings first.
275
+ let actorValue, len = contextList.length;
276
+ for (let i = 0; i < len; i++) {
277
  if (contextList[i].hasOwnProperty(actorId)) {
278
  actorValue = contextList[i][actorId];
279
  if (typeof actorValue === 'boolean') {
290
  }
291
 
292
  //Step #3: Check owned/default capabilities. Always checked.
293
+ let actor = this.getActor(actorId),
294
  hasOwnCap = actor.hasOwnCap(capability);
295
  if (hasOwnCap !== null) {
296
  return hasOwnCap;
306
 
307
  //Check if any of the user's roles have the capability.
308
  result = false;
309
+ for (let index = 0; index < actor.roles.length; index++) {
310
  result = result || this.actorHasCap('role:' + actor.roles[index], capability, contextList);
311
  }
312
  return result;
391
  ) {
392
  capability = AmeActorManager.mapMetaCap(capability);
393
 
394
+ const grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
395
  AmeActorManager._.set(context, [actor, capability], grant);
396
  }
397
 
414
  * the direct grant is redundant. We can remove it. Jane will still have "edit_posts" because she's an editor.
415
  */
416
  pruneGrantedUserCapabilities(): AmeGrantedCapabilityMap {
417
+ let _ = AmeActorManager._,
418
  pruned = _.cloneDeep(this.grantedCapabilities),
419
  context = [pruned];
420
 
421
+ let actorKeys = _(pruned).keys().filter((actorId) => {
422
  //Skip users that are not loaded.
423
+ const actor = this.getActor(actorId);
424
  if (actor === null) {
425
  return false;
426
  }
429
 
430
  _.forEach(actorKeys, (actor) => {
431
  _.forEach(_.keys(pruned[actor]), (capability) => {
432
+ const grant = pruned[actor][capability];
433
  delete pruned[actor][capability];
434
 
435
+ const hasCap = _.isArray(grant) ? grant[0] : grant,
436
  hasCapWhenPruned = this.actorHasCap(actor, capability, context);
437
 
438
  if (hasCap !== hasCapWhenPruned) {
455
  * @return {Number}
456
  */
457
  static compareActorSpecificity(actor1: string, actor2: string): Number {
458
+ let delta = AmeBaseActor.getActorSpecificity(actor1) - AmeBaseActor.getActorSpecificity(actor2);
459
  if (delta !== 0) {
460
  delta = (delta > 0) ? 1 : -1;
461
  }
485
  bCaps = capsByPower(b);
486
 
487
  //Prioritise roles with the highest number of the most powerful capabilities.
488
+ let i = 0, limit = Math.min(aCaps.length, bCaps.length);
489
+ for (; i < limit; i++) {
490
  let delta = bCaps[i].power - aCaps[i].power;
491
  if (delta !== 0) {
492
  return delta;
js/menu-editor.js CHANGED
@@ -1907,7 +1907,7 @@ function setActorAccess(containerNode, actor, allowAccess) {
1907
  }
1908
 
1909
  if (typeof actor === 'string') {
1910
- menuItem.grant_access[actor] = !!allowAccess;
1911
  } else {
1912
  _.assign(menuItem.grant_access, actor);
1913
  }
@@ -3924,6 +3924,7 @@ function ameOnDomReady() {
3924
  event.preventDefault();
3925
 
3926
  var button = $(this),
 
3927
  menuBox = $(this).closest('.ws_main_container').find('.ws_box').first();
3928
 
3929
  if (menuBox.is('#ws_submenu_box')) {
@@ -3931,7 +3932,16 @@ function ameOnDomReady() {
3931
  }
3932
 
3933
  if (menuBox.length > 0) {
3934
- sortMenuItems(menuBox, button.data('sort-direction') || 'asc');
 
 
 
 
 
 
 
 
 
3935
  }
3936
  });
3937
 
@@ -3940,10 +3950,12 @@ function ameOnDomReady() {
3940
  *
3941
  * @param $menuBox A DOM node that contains multiple menu items.
3942
  * @param {string} direction 'asc' or 'desc'
 
3943
  */
3944
- function sortMenuItems($menuBox, direction) {
3945
  var multiplier = (direction === 'desc') ? -1 : 1,
3946
- items = $menuBox.find('.ws_container');
 
3947
 
3948
  //Separators don't have a title, but we don't want them to end up at the top of the list.
3949
  //Instead, lets keep their position the same relative to the previous item.
@@ -3960,8 +3972,8 @@ function ameOnDomReady() {
3960
  }));
3961
 
3962
  function compareMenus(a, b){
3963
- var aTitle = jsTrim($(a).find('.ws_item_title').text()),
3964
- bTitle = jsTrim($(b).find('.ws_item_title').text());
3965
 
3966
  aTitle = aTitle.toLowerCase();
3967
  bTitle = bTitle.toLowerCase();
@@ -3975,6 +3987,11 @@ function ameOnDomReady() {
3975
  }
3976
 
3977
  items.sort(compareMenus);
 
 
 
 
 
3978
  }
3979
 
3980
  //Toggle the second row of toolbar buttons.
1907
  }
1908
 
1909
  if (typeof actor === 'string') {
1910
+ menuItem.grant_access[actor] = Boolean(allowAccess);
1911
  } else {
1912
  _.assign(menuItem.grant_access, actor);
1913
  }
3924
  event.preventDefault();
3925
 
3926
  var button = $(this),
3927
+ direction = button.data('sort-direction') || 'asc',
3928
  menuBox = $(this).closest('.ws_main_container').find('.ws_box').first();
3929
 
3930
  if (menuBox.is('#ws_submenu_box')) {
3932
  }
3933
 
3934
  if (menuBox.length > 0) {
3935
+ sortMenuItems(menuBox, direction);
3936
+ }
3937
+
3938
+ //When sorting the top level menu also sort submenus, but leave the first item unmoved.
3939
+ //Moving the first item would change the parent menu URL (WP always links it to the first item),
3940
+ //which can be unexpected and confusing. The user can always move the first item manually.
3941
+ if (menuBox.is('#ws_menu_box')) {
3942
+ $('#ws_submenu_box').find('.ws_submenu').each(function() {
3943
+ sortMenuItems($(this), direction, true);
3944
+ });
3945
  }
3946
  });
3947
 
3950
  *
3951
  * @param $menuBox A DOM node that contains multiple menu items.
3952
  * @param {string} direction 'asc' or 'desc'
3953
+ * @param {boolean} [leaveFirstItem] Leave the first item in its original position. Defaults to false.
3954
  */
3955
+ function sortMenuItems($menuBox, direction, leaveFirstItem) {
3956
  var multiplier = (direction === 'desc') ? -1 : 1,
3957
+ items = $menuBox.find('.ws_container'),
3958
+ firstItem = items.first();
3959
 
3960
  //Separators don't have a title, but we don't want them to end up at the top of the list.
3961
  //Instead, lets keep their position the same relative to the previous item.
3972
  }));
3973
 
3974
  function compareMenus(a, b){
3975
+ var aTitle = $(a).data('ame-sort-value'),
3976
+ bTitle = $(b).data('ame-sort-value');
3977
 
3978
  aTitle = aTitle.toLowerCase();
3979
  bTitle = bTitle.toLowerCase();
3987
  }
3988
 
3989
  items.sort(compareMenus);
3990
+
3991
+ if (leaveFirstItem) {
3992
+ //Move the first item back to the top.
3993
+ firstItem.prependTo($menuBox);
3994
+ }
3995
  }
3996
 
3997
  //Toggle the second row of toolbar buttons.
menu-editor.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
- Version: 1.7.3
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
+ Version: 1.8
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
modules/actor-selector/actor-selector.js CHANGED
@@ -196,7 +196,7 @@ var AmeActorSelector = (function () {
196
  }
197
  return name;
198
  };
199
- AmeActorSelector._ = wsAmeLodash;
200
  return AmeActorSelector;
201
  }());
 
202
  //# sourceMappingURL=actor-selector.js.map
196
  }
197
  return name;
198
  };
 
199
  return AmeActorSelector;
200
  }());
201
+ AmeActorSelector._ = wsAmeLodash;
202
  //# sourceMappingURL=actor-selector.js.map
modules/actor-selector/actor-selector.php CHANGED
@@ -1,20 +1,17 @@
1
  <?php
2
- class ameActorSelector {
3
  const ajaxUpdateAction = 'ws_ame_set_visible_users';
4
 
5
- /**
6
- * @var WPMenuEditor
7
- */
8
- private $menuEditor;
9
-
10
  public function __construct($menuEditor) {
11
- $this->menuEditor = $menuEditor;
12
 
13
  add_action('wp_ajax_' . self::ajaxUpdateAction, array($this, 'ajaxSetVisibleUsers'));
14
- add_action('admin_menu_editor-register_scripts', array($this, 'registerScripts'));
15
  }
16
 
17
  public function registerScripts() {
 
 
18
  $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
19
  $dependencies = array('ame-actor-manager', 'ame-lodash', 'jquery');
20
  if ( $isProVersion || wp_script_is('ame-visible-users', 'registered') ) {
@@ -58,4 +55,12 @@ class ameActorSelector {
58
  $this->menuEditor->set_plugin_option('visible_users', $visibleUsers);
59
  die('OK');
60
  }
 
 
 
 
 
 
 
 
61
  }
1
  <?php
2
+ class ameActorSelector extends ameModule {
3
  const ajaxUpdateAction = 'ws_ame_set_visible_users';
4
 
 
 
 
 
 
5
  public function __construct($menuEditor) {
6
+ parent::__construct($menuEditor);
7
 
8
  add_action('wp_ajax_' . self::ajaxUpdateAction, array($this, 'ajaxSetVisibleUsers'));
9
+ add_action('admin_menu_editor-users_to_load', array($this, 'addVisibleUsersToLoginList'));
10
  }
11
 
12
  public function registerScripts() {
13
+ parent::registerScripts();
14
+
15
  $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
16
  $dependencies = array('ame-actor-manager', 'ame-lodash', 'jquery');
17
  if ( $isProVersion || wp_script_is('ame-visible-users', 'registered') ) {
55
  $this->menuEditor->set_plugin_option('visible_users', $visibleUsers);
56
  die('OK');
57
  }
58
+
59
+ public function addVisibleUsersToLoginList($userLogins) {
60
+ $visibleUsers = $this->menuEditor->get_plugin_option('visible_users');
61
+ if ( is_array($visibleUsers) ) {
62
+ $userLogins = array_merge($userLogins, $visibleUsers);
63
+ }
64
+ return $userLogins;
65
+ }
66
  }
modules/actor-selector/actor-selector.ts CHANGED
@@ -87,7 +87,7 @@ class AmeActorSelector {
87
 
88
  //Select an actor on click.
89
  this.selectorNode.on('click', 'li a.ws_actor_option', (event) => {
90
- var actor = jQuery(event.target).attr('href').substring(1);
91
  if (actor === '') {
92
  actor = null;
93
  }
@@ -124,13 +124,13 @@ class AmeActorSelector {
124
  return;
125
  }
126
 
127
- var previousSelection = this.selectedActor;
128
  this.selectedActor = actorId;
129
  this.highlightSelectedActor();
130
 
131
  //Notify subscribers that the selection has changed.
132
  if (this.selectedActor !== previousSelection) {
133
- for (var i = 0; i < this.subscribers.length; i++) {
134
  this.subscribers[i](this.selectedActor, previousSelection);
135
  }
136
  }
@@ -145,7 +145,7 @@ class AmeActorSelector {
145
  this.selectorNode.find('.current').removeClass('current');
146
 
147
  //Select the new one or "All".
148
- var selector;
149
  if (this.selectedActor === null) {
150
  selector = 'a.ws_no_actor';
151
  } else {
@@ -157,13 +157,13 @@ class AmeActorSelector {
157
  private populateActorSelector() {
158
  const actorSelector = this.selectorNode,
159
  $ = jQuery;
160
- var isSelectedActorVisible = false;
161
 
162
  //Build the list of available actors.
163
  actorSelector.empty();
164
  actorSelector.append('<li><a href="#" class="current ws_actor_option ws_no_actor" data-text="All">All</a></li>');
165
 
166
- var visibleActors = this.getVisibleActors();
167
  for (let i = 0; i < visibleActors.length; i++) {
168
  const actor = visibleActors[i],
169
  name = this.getNiceName(actor);
@@ -181,7 +181,7 @@ class AmeActorSelector {
181
  }
182
 
183
  if (this.isProVersion) {
184
- var moreUsersText = 'Choose users\u2026';
185
  actorSelector.append(
186
  $('<li>').append(
187
  $('<a></a>')
@@ -211,7 +211,7 @@ class AmeActorSelector {
211
  }
212
 
213
  const _ = AmeActorSelector._;
214
- var actors = [];
215
 
216
  //Include all roles.
217
  //Idea: Sort roles either alphabetically or by typical privilege level (admin, editor, author, ...).
@@ -230,7 +230,7 @@ class AmeActorSelector {
230
  .without(this.currentUserLogin)
231
  .sortBy()
232
  .forEach((login) => {
233
- var user = this.actorManager.getUser(login);
234
  actors.push(user);
235
  })
236
  .value();
87
 
88
  //Select an actor on click.
89
  this.selectorNode.on('click', 'li a.ws_actor_option', (event) => {
90
+ let actor = jQuery(event.target).attr('href').substring(1);
91
  if (actor === '') {
92
  actor = null;
93
  }
124
  return;
125
  }
126
 
127
+ const previousSelection = this.selectedActor;
128
  this.selectedActor = actorId;
129
  this.highlightSelectedActor();
130
 
131
  //Notify subscribers that the selection has changed.
132
  if (this.selectedActor !== previousSelection) {
133
+ for (let i = 0; i < this.subscribers.length; i++) {
134
  this.subscribers[i](this.selectedActor, previousSelection);
135
  }
136
  }
145
  this.selectorNode.find('.current').removeClass('current');
146
 
147
  //Select the new one or "All".
148
+ let selector;
149
  if (this.selectedActor === null) {
150
  selector = 'a.ws_no_actor';
151
  } else {
157
  private populateActorSelector() {
158
  const actorSelector = this.selectorNode,
159
  $ = jQuery;
160
+ let isSelectedActorVisible = false;
161
 
162
  //Build the list of available actors.
163
  actorSelector.empty();
164
  actorSelector.append('<li><a href="#" class="current ws_actor_option ws_no_actor" data-text="All">All</a></li>');
165
 
166
+ const visibleActors = this.getVisibleActors();
167
  for (let i = 0; i < visibleActors.length; i++) {
168
  const actor = visibleActors[i],
169
  name = this.getNiceName(actor);
181
  }
182
 
183
  if (this.isProVersion) {
184
+ const moreUsersText = 'Choose users\u2026';
185
  actorSelector.append(
186
  $('<li>').append(
187
  $('<a></a>')
211
  }
212
 
213
  const _ = AmeActorSelector._;
214
+ let actors = [];
215
 
216
  //Include all roles.
217
  //Idea: Sort roles either alphabetically or by typical privilege level (admin, editor, author, ...).
230
  .without(this.currentUserLogin)
231
  .sortBy()
232
  .forEach((login) => {
233
+ const user = this.actorManager.getUser(login);
234
  actors.push(user);
235
  })
236
  .value();
modules/admin-css/admin-css.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ameAdminCss extends ameModule {
4
+ protected $tabSlug = 'admin-css';
5
+ protected $tabTitle = 'CSS';
6
+
7
+ public function enqueueTabScripts() {
8
+ parent::enqueueTabScripts();
9
+
10
+ $menuConfig = $this->menuEditor->get_active_admin_menu();
11
+
12
+ //We really only need a couple of menu properties for this feature, like the titles and URLs.
13
+ $items = array_values(array_map(array($this, 'getRelevantMenuProperties'), $menuConfig['tree']));
14
+ }
15
+
16
+ private function getRelevantMenuProperties($menuItem) {
17
+ $properties = array(
18
+ 'menu_title' => ameMenuItem::get($menuItem, 'menu_title', '(Untitled Item)'),
19
+ 'url' => ameMenuItem::get($menuItem, 'url'),
20
+ );
21
+
22
+ if ( ameMenuItem::get($menuItem, 'separator', false) ) {
23
+ $properties['separator'] = true;
24
+ }
25
+
26
+ if ( !empty($menuItem['items']) ) {
27
+ $properties['items'] = array_values(array_map(
28
+ array($this, 'getRelevantMenuProperties'),
29
+ $menuItem['items']
30
+ ));
31
+ }
32
+
33
+ return $properties;
34
+ }
35
+ }
modules/highlight-new-menus/assets/highlight-menus.js ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* global wsNmhData */
2
+
3
+ jQuery(function($) {
4
+ 'use strict';
5
+
6
+ var $adminMenu = $('#adminmenu'),
7
+ seenOnThisPage = {},
8
+ foundNewMenus;
9
+
10
+ /**
11
+ * Flag a menu URL as seen (i.e. no longer new).
12
+ *
13
+ * This function is both debounced and throttled. As long as you continue to call it,
14
+ * the AJAX request won't be triggered. It will be sent only once the function stops
15
+ * being called for N milliseconds. Additionally, it will wait at least N milliseconds
16
+ * between requests.
17
+ *
18
+ * @param string menuUrls
19
+ */
20
+ var flagAsSeen = (function() {
21
+ var queue = [],
22
+ timeout = null,
23
+ waitMs = 500,
24
+ isRequestInProgress = false;
25
+
26
+ function sendQueuedUrls() {
27
+
28
+ if (queue.length > 0) {
29
+ if (isRequestInProgress) {
30
+ //console.warn('A request is already in progress');
31
+ }
32
+ isRequestInProgress = true;
33
+
34
+ $.ajax(
35
+ ajaxurl,
36
+ {
37
+ 'method': 'POST',
38
+ 'data': {
39
+ 'action': wsNmhData['flagAction'],
40
+ '_ajax_nonce': wsNmhData['flagNonce'],
41
+ 'urls' : JSON.stringify(queue)
42
+ },
43
+ 'complete': function() {
44
+ isRequestInProgress = false;
45
+ if (queue.length > 0) {
46
+ timeout = setTimeout(sendQueuedUrls, waitMs);
47
+ }
48
+ }
49
+ }
50
+ );
51
+ }
52
+
53
+ queue = [];
54
+ timeout = null;
55
+ }
56
+
57
+ return function(menuUrl) {
58
+ if ((menuUrl === '') || seenOnThisPage.hasOwnProperty(menuUrl)) {
59
+ return;
60
+ }
61
+ seenOnThisPage[menuUrl] = true;
62
+
63
+ //In case the AJAX request doesn't succeed for whatever reason, lets *also*
64
+ //set a session cookie that contains the seen URLs.
65
+ if ($.cookie) {
66
+ $.cookie('ws_nmh_pending_seen_urls', JSON.stringify(seenOnThisPage));
67
+ }
68
+
69
+ queue.push(menuUrl);
70
+
71
+ if (isRequestInProgress) {
72
+ return;
73
+ }
74
+
75
+ if (timeout) {
76
+ //Move the timeout forward.
77
+ clearTimeout(timeout);
78
+ }
79
+ timeout = setTimeout(sendQueuedUrls, waitMs);
80
+ }
81
+ })();
82
+
83
+ function maybeFlagItem($item) {
84
+ var shouldFlagThis = true,
85
+ shouldFlagParent = false,
86
+ $link, $flag, $parent;
87
+
88
+ if ($item.hasClass('menu-top')) {
89
+ //Only flag top level menus when they no longer have any new submenus.
90
+ shouldFlagThis = $item
91
+ .find('> ul li.ws-nmh-is-new-menu')
92
+ .not('.wp-submenu-head')
93
+ .length === 0;
94
+ } else {
95
+ //If all of the submenus on this level are seen/not new, flag the parent menu as well.
96
+ shouldFlagParent = $item.siblings('li.ws-nmh-is-new-menu').not('.wp-submenu-head').length === 0;
97
+ }
98
+
99
+ if (!shouldFlagThis) {
100
+ return;
101
+ }
102
+
103
+ $link = $item.children('a').first();
104
+ $flag = $link.find('.ws-nmh-new-menu-flag');
105
+
106
+ $item.removeClass('ws-nmh-is-new-menu');
107
+ $link.removeClass('ws-nmh-is-new-menu');
108
+
109
+ if ($flag.length > 0) {
110
+ flagAsSeen($flag.data('nmh-menu-url'));
111
+ $flag.remove();
112
+ }
113
+
114
+ if (shouldFlagParent) {
115
+ $parent = $item.parentsUntil($adminMenu, 'li');
116
+ if (($parent.length > 0) && !$parent.is($item)) {
117
+ maybeFlagItem($parent);
118
+ }
119
+ }
120
+ }
121
+
122
+ var $newItems = $adminMenu.find('.ws-nmh-new-menu-flag').closest('li').addClass('ws-nmh-is-new-menu');
123
+ foundNewMenus = $newItems.length > 0;
124
+
125
+ //Flag the current item as seen.
126
+ $newItems.filter('.current, .wp-has-current-submenu').each(function() {
127
+ maybeFlagItem($(this));
128
+ });
129
+
130
+ if (foundNewMenus) {
131
+ $adminMenu.on('mouseenter click focusin', 'li.ws-nmh-is-new-menu', function() {
132
+ maybeFlagItem($(this).closest('li'));
133
+ });
134
+ }
135
+ });
modules/highlight-new-menus/assets/menu-highlights.css ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #adminmenu a.ws-nmh-is-new-menu,
2
+ #adminmenu li.ws-nmh-is-new-menu > a.menu-top,
3
+ #adminmenu .wp-submenu > li.ws-nmh-is-new-menu {
4
+ background-color: #73aa00 !important; }
5
+ #adminmenu li.ws-nmh-is-new-menu > a {
6
+ color: #ffe !important; }
7
+ #adminmenu li.ws-nmh-is-new-menu > a div.wp-menu-image::before {
8
+ color: rgba(255, 255, 238, 0.6); }
9
+ #adminmenu .ws-nmh-new-menu-flag {
10
+ display: none; }
11
+
12
+ /*# sourceMappingURL=menu-highlights.css.map */
modules/highlight-new-menus/assets/menu-highlights.css.map ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ {
2
+ "version": 3,
3
+ "mappings": "AAIC;;8CAEoC;EACnC,gBAAgB,EAAE,kBAAsB;AAGzC,oCAA0B;EACzB,KAAK,EAAE,eAAgB;EAEvB,8DAA0B;IACzB,KAAK,EAAE,wBAAgB;AAIzB,gCAAsB;EACrB,OAAO,EAAE,IAAI",
4
+ "sources": ["menu-highlights.scss"],
5
+ "names": [],
6
+ "file": "menu-highlights.css"
7
+ }
modules/highlight-new-menus/assets/menu-highlights.scss ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #adminmenu {
2
+ $background: #73aa00;
3
+ $text: #ffe;
4
+
5
+ a.ws-nmh-is-new-menu,
6
+ li.ws-nmh-is-new-menu > a.menu-top,
7
+ .wp-submenu > li.ws-nmh-is-new-menu {
8
+ background-color: $background !important;
9
+ }
10
+
11
+ li.ws-nmh-is-new-menu > a {
12
+ color: $text !important;
13
+
14
+ div.wp-menu-image::before {
15
+ color: rgba($text, 0.6);
16
+ }
17
+ }
18
+
19
+ .ws-nmh-new-menu-flag {
20
+ display: none;
21
+ }
22
+ }
23
+
modules/highlight-new-menus/highlight-new-menus.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ameMenuHighlighterWrapper extends ameModule {
4
+ public function __construct($menuEditor) {
5
+ parent::__construct($menuEditor);
6
+
7
+ if ( is_admin() ) {
8
+ require dirname(__FILE__) . '/wsNewMenuHighlighter.php';
9
+ new wsNewMenuHighlighter();
10
+ }
11
+ }
12
+ }
modules/highlight-new-menus/plugin.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Plugin Name: Highlight New Admin Menus
5
+ * Plugin URI: http://adminmenueditor.com
6
+ * Version: 1.0
7
+ * Author: Janis Elsts
8
+ * Author URI: http://w-shadow.com/
9
+ * Description: Highlights new admin menu items.
10
+ */
11
+
12
+ require 'wsNewMenuHighlighter.php';
13
+ if ( is_admin() ) {
14
+ new wsNewMenuHighlighter();
15
+ }
modules/highlight-new-menus/uninstall.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+ //Remove "user has seen this menu" records.
3
+ if ( defined('ABSPATH') && defined('WP_UNINSTALL_PLUGIN') ) {
4
+ delete_metadata('user', 0, 'ws_nmh_seen_menus', null, true);
5
+ }
modules/highlight-new-menus/wsNewMenuHighlighter.php ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wsNewMenuHighlighter {
4
+ const ITEM_SLUG_INDEX = 2;
5
+ const ITEM_TITLE_INDEX = 0;
6
+ const ITEM_CLASS_INDEX = 4;
7
+
8
+ const STORAGE_KEY = 'ws_nmh_seen_menus';
9
+ const AJAX_FLAG_ACTION = 'nmh-flag-as-seen';
10
+ const COOKIE_NAME = 'ws_nmh_pending_seen_urls';
11
+
12
+ static $blacklist = array(
13
+ //These items are invisible when the theme customizer is available.
14
+ 'themes.php?page=custom-header' => true,
15
+ 'themes.php?page=custom-background' => true,
16
+ //Files in /wp-admin.
17
+ 'customize.php' => true,
18
+ 'edit-comments.php' => true,
19
+ 'edit-tags.php' => true,
20
+ 'edit.php' => true,
21
+ 'export.php' => true,
22
+ 'import.php' => true,
23
+ 'index.php' => true,
24
+ 'link-add.php' => true,
25
+ 'link-manager.php' => true,
26
+ 'media-new.php' => true,
27
+ 'nav-menus.php' => true,
28
+ 'options-discussion.php' => true,
29
+ 'options-general.php' => true,
30
+ 'options-media.php' => true,
31
+ 'options-permalink.php' => true,
32
+ 'options-reading.php' => true,
33
+ 'options-writing.php' => true,
34
+ 'plugin-editor.php' => true,
35
+ 'plugin-install.php' => true,
36
+ 'plugins.php' => true,
37
+ 'post-new.php' => true,
38
+ 'profile.php' => true,
39
+ 'theme-editor.php' => true,
40
+ 'themes.php' => true,
41
+ 'tools.php' => true,
42
+ 'update-core.php' => true,
43
+ 'upload.php' => true,
44
+ 'user-new.php' => true,
45
+ 'users.php' => true,
46
+ 'widgets.php' => true,
47
+ );
48
+
49
+ private $menusWithNewSubmenus = array();
50
+ private $seenMenuUrls = array();
51
+ private $isFirstRun = false;
52
+
53
+ public function __construct() {
54
+ //Run after AME replaces the menu so that we don't pollute the menu editor with our flags and classes.
55
+ if ( class_exists('WPMenuEditor', false) ) {
56
+ add_action('admin_menu_editor-menu_replaced', array($this, 'parseAdminMenu'));
57
+ add_action('admin_menu_editor-menu_replacement_skipped', array($this, 'parseAdminMenu'));
58
+ } else {
59
+ add_action('admin_menu', array($this, 'parseAdminMenu'), 9000);
60
+ }
61
+
62
+ add_action('admin_enqueue_scripts', array($this, 'enqueueDependencies'));
63
+ add_action('wp_ajax_' . self::AJAX_FLAG_ACTION, array($this, 'ajaxFlagAsSeen'));
64
+
65
+ add_action('admin_init', array($this, 'flagUrlsFromCookie'));
66
+ }
67
+
68
+ public function parseAdminMenu() {
69
+ if ( !current_user_can('activate_plugins') ) {
70
+ return;
71
+ }
72
+
73
+ global $menu, $submenu;
74
+ $this->seenMenuUrls = $this->loadSeenMenus();
75
+
76
+ if ( empty($this->seenMenuUrls) ) {
77
+ $this->isFirstRun = true;
78
+ }
79
+
80
+ foreach ($submenu as $parent => &$items) {
81
+ foreach ($items as &$submenuItem) {
82
+ $submenuItem = $this->processItem($submenuItem, $parent);
83
+ }
84
+ }
85
+ unset($items, $submenuItem);
86
+
87
+ foreach ($menu as &$item) {
88
+ $item = $this->processItem($item);
89
+ }
90
+
91
+ if ( $this->isFirstRun ) {
92
+ $urls = array_keys($this->seenMenuUrls);
93
+ $this->seenMenuUrls = array();
94
+ $this->flagAsSeen($urls);
95
+ }
96
+ }
97
+
98
+ private function loadSeenMenus() {
99
+ $seenMenuUrls = get_user_meta(get_current_user_id(), self::STORAGE_KEY, true);
100
+ if ( !is_array($seenMenuUrls) ) {
101
+ $seenMenuUrls = array();
102
+ }
103
+ return $seenMenuUrls;
104
+ }
105
+
106
+ private function processItem($item, $parentSlug = null) {
107
+ if ( $this->isIgnoredItem($item) ) {
108
+ return $item;
109
+ }
110
+
111
+ $itemSlug = $item[self::ITEM_SLUG_INDEX];
112
+ $url = $this->getMenuUrl($itemSlug, $parentSlug);
113
+ $isBlacklisted = empty($url) || isset(self::$blacklist[$url]);
114
+
115
+ //On first run, just collect all items and flag them as seen.
116
+ if ( $this->isFirstRun ) {
117
+ if ( !$isBlacklisted ) {
118
+ $this->seenMenuUrls[$url] = true;
119
+ }
120
+ return $item;
121
+ }
122
+
123
+ if ( ($this->isNewMenu($url) && !$isBlacklisted) || $this->hasNewSubmenus($itemSlug) ) {
124
+ $item[self::ITEM_TITLE_INDEX] .= sprintf(
125
+ '<span class="ws-nmh-new-menu-flag" data-nmh-menu-url="%s"></span>',
126
+ esc_attr($url)
127
+ );
128
+
129
+ if ( ($parentSlug === null) && isset($item[self::ITEM_CLASS_INDEX]) ) {
130
+ $item[self::ITEM_CLASS_INDEX] .= ' ws-nmh-is-new-menu';
131
+ }
132
+
133
+ if ( $parentSlug !== null ) {
134
+ $this->menusWithNewSubmenus[$parentSlug] = true;
135
+ }
136
+ }
137
+
138
+ return $item;
139
+ }
140
+
141
+ private function getMenuUrl($itemSlug, $parentSlug) {
142
+ if ( class_exists('ameMenuItem') ) {
143
+ return ameMenuItem::generate_url($itemSlug, $parentSlug);
144
+ } else {
145
+ return $itemSlug;
146
+ }
147
+ }
148
+
149
+ private function isIgnoredItem($item) {
150
+ if ( !isset($item[self::ITEM_SLUG_INDEX]) ) {
151
+ return true; //That's either an invalid item or an improvised separator.
152
+ }
153
+
154
+ //Skip separators and unnamed menus.
155
+ $isSeparator = isset($item[self::ITEM_CLASS_INDEX])
156
+ && (strpos($item[self::ITEM_CLASS_INDEX], 'wp-menu-separator') !== false);
157
+
158
+ if ( $isSeparator || empty($item[self::ITEM_SLUG_INDEX]) || ($item[self::ITEM_TITLE_INDEX] === '') ) {
159
+ return true;
160
+ }
161
+
162
+ //Skip customizer links. They have a different URL on every admin page, so they'd always show up as new.
163
+ if ( strpos($item[self::ITEM_SLUG_INDEX], 'customize.php') === 0 ) {
164
+ return true;
165
+ }
166
+
167
+ return false;
168
+ }
169
+
170
+ private function isNewMenu($url) {
171
+ return empty($this->seenMenuUrls[$url]);
172
+ }
173
+
174
+ private function hasNewSubmenus($slug) {
175
+ return !empty($this->menusWithNewSubmenus[$slug]);
176
+ }
177
+
178
+ public function enqueueDependencies() {
179
+ $dependencies = array('jquery');
180
+
181
+ if ( isset($GLOBALS['wp_menu_editor']) && is_callable(array(
182
+ $GLOBALS['wp_menu_editor'],
183
+ 'register_base_dependencies',
184
+ ))
185
+ ) {
186
+ $GLOBALS['wp_menu_editor']->register_base_dependencies();
187
+ $dependencies[] = 'jquery-cookie';
188
+ }
189
+
190
+ wp_enqueue_script(
191
+ 'ws-nmh-admin-script',
192
+ plugins_url('assets/highlight-menus.js', __FILE__),
193
+ $dependencies,
194
+ '20170503'
195
+ );
196
+
197
+ wp_localize_script(
198
+ 'ws-nmh-admin-script',
199
+ 'wsNmhData',
200
+ array(
201
+ 'flagAction' => self::AJAX_FLAG_ACTION,
202
+ 'flagNonce' => wp_create_nonce(self::AJAX_FLAG_ACTION),
203
+ )
204
+ );
205
+
206
+ wp_enqueue_style(
207
+ 'ws-nmh-admin-style',
208
+ plugins_url('assets/menu-highlights.css', __FILE__),
209
+ array(),
210
+ '20170503'
211
+ );
212
+ }
213
+
214
+ public function ajaxFlagAsSeen() {
215
+ check_ajax_referer(self::AJAX_FLAG_ACTION);
216
+ if ( empty($_POST['urls']) ) {
217
+ if ( function_exists('status_header') ) {
218
+ status_header(400);
219
+ }
220
+ exit('Error: The required "urls" parameter is missing.');
221
+ }
222
+
223
+ $json = strval($_POST['urls']);
224
+ //Unfortunately, WP applies magic quotes to POST data.
225
+ if ( function_exists('wp_magic_quotes') && did_action('plugins_loaded') ) {
226
+ $json = stripslashes($json);
227
+ }
228
+
229
+ if ( $this->flagAsSeen(json_decode($json)) ) {
230
+ exit('Success');
231
+ } else {
232
+ exit('Failure');
233
+ }
234
+ }
235
+
236
+ public function flagUrlsFromCookie() {
237
+ if ( !is_user_logged_in() || empty($_COOKIE[self::COOKIE_NAME]) || defined('DOING_AJAX') ) {
238
+ return;
239
+ }
240
+
241
+ $urls = json_decode(stripslashes($_COOKIE[self::COOKIE_NAME]), true);
242
+ if ( is_array($urls) ) {
243
+ $this->flagAsSeen(array_keys($urls));
244
+ }
245
+
246
+ setcookie(self::COOKIE_NAME, '', time() - (24 * 3600));
247
+ }
248
+
249
+ private function flagAsSeen($menuUrls) {
250
+ if ( empty($menuUrls) || !is_array($menuUrls) ) {
251
+ return false;
252
+ }
253
+ $menuUrls = array_filter($menuUrls);
254
+ $this->seenMenuUrls = $this->loadSeenMenus();
255
+
256
+ //Optimization: Save only if there are changes / new URLs.
257
+ $urlIndex = array_fill_keys($menuUrls, true);
258
+ $newUrls = array_diff_key($urlIndex, $this->seenMenuUrls);
259
+ if ( !empty($newUrls) ) {
260
+ $this->seenMenuUrls = array_merge($this->seenMenuUrls, $urlIndex);
261
+ return update_user_meta(get_current_user_id(), self::STORAGE_KEY, $this->seenMenuUrls);
262
+ } else {
263
+ return false;
264
+ }
265
+ }
266
+ }
modules/plugin-visibility/plugin-visibility-template.php CHANGED
@@ -41,7 +41,8 @@
41
  css: {
42
  'active': isActive,
43
  'inactive': !isActive
44
- }
 
45
  ">
46
 
47
  <!--
@@ -64,10 +65,69 @@
64
  <label data-bind="attr: { 'for': 'ame-plugin-visible-' + $index() }">
65
  <strong data-bind="text: name"></strong>
66
  </label>
 
 
 
 
 
 
67
  </td>
68
 
69
  <td><p data-bind="text: description"></p></td>
70
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  </tbody>
72
 
73
  <tfoot>
41
  css: {
42
  'active': isActive,
43
  'inactive': !isActive
44
+ },
45
+ visible: !isBeingEdited()
46
  ">
47
 
48
  <!--
65
  <label data-bind="attr: { 'for': 'ame-plugin-visible-' + $index() }">
66
  <strong data-bind="text: name"></strong>
67
  </label>
68
+ <div class="row-actions">
69
+ <span class="edit">
70
+ <a href="#" title="Edit plugin name and description. This is a cosmetic change - the actual plugin files are not affected."
71
+ data-bind="click: openInlineEditor.bind($data)">Edit</a>
72
+ </span>
73
+ </div>
74
  </td>
75
 
76
  <td><p data-bind="text: description"></p></td>
77
  </tr>
78
+ <tr class="inline-edit-row" data-bind="if: isBeingEdited">
79
+ <td class="colspanchange" colspan="3">
80
+ <fieldset class="ame-pv-inline-edit-left">
81
+ <div class="inline-edit-col">
82
+ <label>
83
+ <span class="title">Name</span>
84
+ <input type="text" data-bind="value: editableName" class="ame-pv-custom-name">
85
+ </label>
86
+ </div>
87
+ </fieldset>
88
+ <fieldset class="ame-pv-inline-edit-right">
89
+ <div class="inline-edit-col">
90
+ <label>
91
+ <span class="title">Description</span>
92
+ <textarea name="plugin-description" cols="30" rows="5"
93
+ class="ame-pv-custom-description"
94
+ data-bind="value: editableDescription"></textarea>
95
+ </label>
96
+ </div>
97
+ </fieldset>
98
+
99
+ <p class="submit">
100
+ <?php
101
+ submit_button(
102
+ 'Cancel',
103
+ 'secondary cancel alignleft',
104
+ 'pv-cancel',
105
+ false,
106
+ array(
107
+ 'data-bind' => 'click: cancelEdit.bind($data)'
108
+ )
109
+ );
110
+ ?>
111
+
112
+ <a class="alignleft ame-pv-inline-reset" href="#"
113
+ title="Reset name and description to default values"
114
+ data-bind="click: resetNameAndDescription.bind($data)">Reset to default</a>
115
+
116
+ <?php
117
+ submit_button(
118
+ 'Update',
119
+ 'primary save alignright',
120
+ 'pv-update',
121
+ false,
122
+ array(
123
+ 'data-bind' => 'click: confirmEdit.bind($data)'
124
+ )
125
+ );
126
+ ?>
127
+ <br class="clear">
128
+ </p>
129
+ </td>
130
+ </tr>
131
  </tbody>
132
 
133
  <tfoot>
modules/plugin-visibility/plugin-visibility.css CHANGED
@@ -41,4 +41,23 @@
41
  #ws_actor_selector_container {
42
  margin-right: 130px; }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  /*# sourceMappingURL=plugin-visibility.css.map */
41
  #ws_actor_selector_container {
42
  margin-right: 130px; }
43
 
44
+ /*
45
+ Inline editor
46
+ */
47
+ #ame-plugin-visibility-editor .inline-edit-row .ame-pv-inline-edit-left {
48
+ width: 30%; }
49
+ #ame-plugin-visibility-editor .inline-edit-row .ame-pv-inline-edit-right {
50
+ width: 70%; }
51
+ #ame-plugin-visibility-editor .inline-edit-row span.title {
52
+ width: auto; }
53
+ #ame-plugin-visibility-editor .inline-edit-row input[type="text"] {
54
+ width: 100%; }
55
+ #ame-plugin-visibility-editor .inline-edit-row .ame-pv-inline-reset {
56
+ line-height: 28px;
57
+ margin-left: 1em; }
58
+ #ame-plugin-visibility-editor .inline-edit-row td {
59
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1); }
60
+ #ame-plugin-visibility-editor .inline-edit-row p.submit {
61
+ margin-bottom: 0.2em; }
62
+
63
  /*# sourceMappingURL=plugin-visibility.css.map */
modules/plugin-visibility/plugin-visibility.js CHANGED
@@ -3,6 +3,7 @@
3
  /// <reference path="../../js/jqueryui.d.ts" />
4
  /// <reference path="../../js/lodash-3.10.d.ts" />
5
  /// <reference path="../../modules/actor-selector/actor-selector.ts" />
 
6
  var AmePluginVisibilityModule = (function () {
7
  function AmePluginVisibilityModule(scriptData) {
8
  var _this = this;
@@ -32,6 +33,10 @@ var AmePluginVisibilityModule = (function () {
32
  this.plugins = _.map(scriptData.installedPlugins, function (plugin) {
33
  return new AmePlugin(plugin, _.get(scriptData.settings.plugins, plugin.fileName, {}), _this);
34
  });
 
 
 
 
35
  this.privilegedActors = [this.actorSelector.getCurrentUserActor()];
36
  if (this.isMultisite) {
37
  this.privilegedActors.push(AmeActors.getSuperAdmin());
@@ -132,11 +137,14 @@ var AmePluginVisibilityModule = (function () {
132
  isVisibleByDefault: plugin.isVisibleByDefault(),
133
  grantAccess: _.mapValues(plugin.grantAccess, function (allow) {
134
  return allow();
135
- })
 
 
136
  };
137
  });
138
  return result;
139
  };
 
140
  AmePluginVisibilityModule.prototype.saveChanges = function () {
141
  var settings = this.getSettings();
142
  //Remove settings associated with roles and users that no longer exist or are not visible.
@@ -152,16 +160,35 @@ var AmePluginVisibilityModule = (function () {
152
  return AmePluginVisibilityModule;
153
  }());
154
  var AmePlugin = (function () {
155
- function AmePlugin(details, visibility, module) {
156
  var _this = this;
157
- this.name = AmePlugin.stripAllTags(details.name);
158
- this.description = AmePlugin.stripAllTags(details.description);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  this.fileName = details.fileName;
160
  this.isActive = details.isActive;
161
- var _ = AmePluginVisibilityModule._;
162
- this.isVisibleByDefault = ko.observable(_.get(visibility, 'isVisibleByDefault', true));
 
 
163
  var emptyGrant = {};
164
- this.grantAccess = _.mapValues(_.get(visibility, 'grantAccess', emptyGrant), function (hasAccess) {
165
  return ko.observable(hasAccess);
166
  });
167
  this.isChecked = ko.computed({
@@ -180,6 +207,34 @@ var AmePlugin = (function () {
180
  }
181
  return this.grantAccess[actorId];
182
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  AmePlugin.stripAllTags = function (input) {
184
  //Based on: http://phpjs.org/functions/strip_tags/
185
  var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
@@ -192,10 +247,7 @@ jQuery(function ($) {
192
  ko.applyBindings(amePluginVisibility, document.getElementById('ame-plugin-visibility-editor'));
193
  //Permanently dismiss the usage hint via AJAX.
194
  $('#ame-pv-usage-notice').on('click', '.notice-dismiss', function () {
195
- $.post(wsPluginVisibilityData.adminAjaxUrl, {
196
- 'action': 'ws_ame_dismiss_pv_usage_notice',
197
- '_ajax_nonce': wsPluginVisibilityData.dismissNoticeNonce
198
- });
199
  });
200
  });
201
  //# sourceMappingURL=plugin-visibility.js.map
3
  /// <reference path="../../js/jqueryui.d.ts" />
4
  /// <reference path="../../js/lodash-3.10.d.ts" />
5
  /// <reference path="../../modules/actor-selector/actor-selector.ts" />
6
+ /// <reference path="../../ajax-wrapper/ajax-action-wrapper.d.ts" />
7
  var AmePluginVisibilityModule = (function () {
8
  function AmePluginVisibilityModule(scriptData) {
9
  var _this = this;
33
  this.plugins = _.map(scriptData.installedPlugins, function (plugin) {
34
  return new AmePlugin(plugin, _.get(scriptData.settings.plugins, plugin.fileName, {}), _this);
35
  });
36
+ //Normally, the plugin list is sorted by the (real) plugin name. Re-sort taking custom names into account.
37
+ this.plugins.sort(function (a, b) {
38
+ return a.name().localeCompare(b.name());
39
+ });
40
  this.privilegedActors = [this.actorSelector.getCurrentUserActor()];
41
  if (this.isMultisite) {
42
  this.privilegedActors.push(AmeActors.getSuperAdmin());
137
  isVisibleByDefault: plugin.isVisibleByDefault(),
138
  grantAccess: _.mapValues(plugin.grantAccess, function (allow) {
139
  return allow();
140
+ }),
141
+ customName: plugin.customName(),
142
+ customDescription: plugin.customDescription()
143
  };
144
  });
145
  return result;
146
  };
147
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
148
  AmePluginVisibilityModule.prototype.saveChanges = function () {
149
  var settings = this.getSettings();
150
  //Remove settings associated with roles and users that no longer exist or are not visible.
160
  return AmePluginVisibilityModule;
161
  }());
162
  var AmePlugin = (function () {
163
+ function AmePlugin(details, settings, module) {
164
  var _this = this;
165
+ var _ = AmePluginVisibilityModule._;
166
+ this.defaultName = ko.observable(details.name);
167
+ this.defaultDescription = ko.observable(details.description);
168
+ this.customName = ko.observable(_.get(settings, 'customName', ''));
169
+ this.customDescription = ko.observable(_.get(settings, 'customDescription', ''));
170
+ this.name = ko.computed(function () {
171
+ var value = _this.customName();
172
+ if (value === '') {
173
+ value = _this.defaultName();
174
+ }
175
+ return AmePlugin.stripAllTags(value);
176
+ });
177
+ this.description = ko.computed(function () {
178
+ var value = _this.customDescription();
179
+ if (value === '') {
180
+ value = _this.defaultDescription();
181
+ }
182
+ return AmePlugin.stripAllTags(value);
183
+ });
184
  this.fileName = details.fileName;
185
  this.isActive = details.isActive;
186
+ this.isBeingEdited = ko.observable(false);
187
+ this.editableName = ko.observable(this.defaultName());
188
+ this.editableDescription = ko.observable(this.defaultDescription());
189
+ this.isVisibleByDefault = ko.observable(_.get(settings, 'isVisibleByDefault', true));
190
  var emptyGrant = {};
191
+ this.grantAccess = _.mapValues(_.get(settings, 'grantAccess', emptyGrant), function (hasAccess) {
192
  return ko.observable(hasAccess);
193
  });
194
  this.isChecked = ko.computed({
207
  }
208
  return this.grantAccess[actorId];
209
  };
210
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
211
+ AmePlugin.prototype.openInlineEditor = function () {
212
+ this.editableName(this.customName() === '' ? this.defaultName() : this.customName());
213
+ this.editableDescription(this.customDescription() === '' ? this.defaultDescription() : this.customDescription());
214
+ this.isBeingEdited(true);
215
+ };
216
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
217
+ AmePlugin.prototype.cancelEdit = function () {
218
+ this.isBeingEdited(false);
219
+ };
220
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
221
+ AmePlugin.prototype.confirmEdit = function () {
222
+ this.customName(this.editableName());
223
+ this.customDescription(this.editableDescription());
224
+ if (this.customName() === this.defaultName()) {
225
+ this.customName('');
226
+ }
227
+ if (this.customDescription() === this.defaultDescription()) {
228
+ this.customDescription('');
229
+ }
230
+ this.isBeingEdited(false);
231
+ };
232
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
233
+ AmePlugin.prototype.resetNameAndDescription = function () {
234
+ this.customName('');
235
+ this.customDescription('');
236
+ this.isBeingEdited(false);
237
+ };
238
  AmePlugin.stripAllTags = function (input) {
239
  //Based on: http://phpjs.org/functions/strip_tags/
240
  var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
247
  ko.applyBindings(amePluginVisibility, document.getElementById('ame-plugin-visibility-editor'));
248
  //Permanently dismiss the usage hint via AJAX.
249
  $('#ame-pv-usage-notice').on('click', '.notice-dismiss', function () {
250
+ AjawV1.getAction('ws_ame_dismiss_pv_usage_notice').request();
 
 
 
251
  });
252
  });
253
  //# sourceMappingURL=plugin-visibility.js.map
modules/plugin-visibility/plugin-visibility.php CHANGED
@@ -13,6 +13,8 @@ class amePluginVisibility {
13
  private $menuEditor;
14
  private $settings = array();
15
 
 
 
16
  public function __construct($menuEditor) {
17
  $this->menuEditor = $menuEditor;
18
  self::$lastInstance = $this;
@@ -36,10 +38,11 @@ class amePluginVisibility {
36
 
37
  //Display a usage hint in our tab.
38
  add_action('admin_notices', array($this, 'displayUsageNotice'));
39
- $dismissNoticeAction = new ameAjaxAction('ws_ame_dismiss_pv_usage_notice');
40
- $dismissNoticeAction
41
- ->setAuthCallback(array($this->menuEditor, 'current_user_can_edit_menu'))
42
- ->setHandler(array($this, 'ajaxDismissUsageNotice'));
 
43
  }
44
 
45
  public function getSettings() {
@@ -187,12 +190,24 @@ class amePluginVisibility {
187
  */
188
  public function filterPluginList($plugins) {
189
  $user = wp_get_current_user();
 
190
 
191
- //Remove all hidden plugins.
192
  $pluginFileNames = array_keys($plugins);
193
  foreach($pluginFileNames as $fileName) {
 
194
  if ( !$this->isPluginVisible($fileName, $user) ) {
195
  unset($plugins[$fileName]);
 
 
 
 
 
 
 
 
 
 
 
196
  }
197
  }
198
 
@@ -311,7 +326,10 @@ class amePluginVisibility {
311
  wp_register_auto_versioned_script(
312
  'ame-plugin-visibility',
313
  plugins_url('plugin-visibility.js', __FILE__),
314
- array('ame-lodash', 'knockout', 'ame-actor-selector', 'jquery-json',)
 
 
 
315
  );
316
  wp_enqueue_script('ame-plugin-visibility');
317
 
@@ -348,6 +366,9 @@ class amePluginVisibility {
348
  'name' => $header['Name'],
349
  'description' => isset($header['Description']) ? $header['Description'] : '',
350
  'isActive' => $isActive || $isActiveForNetwork,
 
 
 
351
  );
352
  }
353
 
@@ -364,9 +385,6 @@ class amePluginVisibility {
364
  'canManagePlugins' => $canManagePlugins,
365
  'isMultisite' => is_multisite(),
366
  'isProVersion' => $this->menuEditor->is_pro_version(),
367
-
368
- 'dismissNoticeNonce' => wp_create_nonce('ws_ame_dismiss_pv_usage_notice'),
369
- 'adminAjaxUrl' => admin_url('admin-ajax.php'),
370
  );
371
  }
372
 
13
  private $menuEditor;
14
  private $settings = array();
15
 
16
+ private $dismissNoticeAction;
17
+
18
  public function __construct($menuEditor) {
19
  $this->menuEditor = $menuEditor;
20
  self::$lastInstance = $this;
38
 
39
  //Display a usage hint in our tab.
40
  add_action('admin_notices', array($this, 'displayUsageNotice'));
41
+ $this->dismissNoticeAction = ajaw_v1_CreateAction('ws_ame_dismiss_pv_usage_notice')
42
+ ->handler(array($this, 'ajaxDismissUsageNotice'))
43
+ ->permissionCallback(array($this->menuEditor, 'current_user_can_edit_menu'))
44
+ ->method('post')
45
+ ->register();
46
  }
47
 
48
  public function getSettings() {
190
  */
191
  public function filterPluginList($plugins) {
192
  $user = wp_get_current_user();
193
+ $settings = $this->getSettings();
194
 
 
195
  $pluginFileNames = array_keys($plugins);
196
  foreach($pluginFileNames as $fileName) {
197
+ //Remove all hidden plugins.
198
  if ( !$this->isPluginVisible($fileName, $user) ) {
199
  unset($plugins[$fileName]);
200
+ continue;
201
+ }
202
+
203
+ //Set custom names and descriptions.
204
+ $customName = ameUtils::get($settings, array('plugins', $fileName, 'customName'), '');
205
+ $customDescription = ameUtils::get($settings, array('plugins', $fileName, 'customDescription'), '');
206
+ if ( $customName !== '' ) {
207
+ $plugins[$fileName]['Name'] = $customName;
208
+ }
209
+ if ( $customDescription !== '' ) {
210
+ $plugins[$fileName]['Description'] = $customDescription;
211
  }
212
  }
213
 
326
  wp_register_auto_versioned_script(
327
  'ame-plugin-visibility',
328
  plugins_url('plugin-visibility.js', __FILE__),
329
+ array(
330
+ 'ame-lodash', 'knockout', 'ame-actor-selector', 'jquery-json',
331
+ $this->dismissNoticeAction->getScriptHandle(),
332
+ )
333
  );
334
  wp_enqueue_script('ame-plugin-visibility');
335
 
366
  'name' => $header['Name'],
367
  'description' => isset($header['Description']) ? $header['Description'] : '',
368
  'isActive' => $isActive || $isActiveForNetwork,
369
+
370
+ 'customName' => '',
371
+ 'customDescription' => '',
372
  );
373
  }
374
 
385
  'canManagePlugins' => $canManagePlugins,
386
  'isMultisite' => is_multisite(),
387
  'isProVersion' => $this->menuEditor->is_pro_version(),
 
 
 
388
  );
389
  }
390
 
modules/plugin-visibility/plugin-visibility.scss CHANGED
@@ -50,4 +50,37 @@
50
  //Make room for th save button.
51
  #ws_actor_selector_container {
52
  margin-right: 130px;
53
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  //Make room for th save button.
51
  #ws_actor_selector_container {
52
  margin-right: 130px;
53
+ }
54
+
55
+ /*
56
+ Inline editor
57
+ */
58
+
59
+ #ame-plugin-visibility-editor .inline-edit-row {
60
+ .ame-pv-inline-edit-left {
61
+ width: 30%;
62
+ }
63
+ .ame-pv-inline-edit-right {
64
+ width: 70%;
65
+ }
66
+
67
+ span.title {
68
+ width: auto;
69
+ }
70
+ input[type="text"] {
71
+ width: 100%;
72
+ }
73
+
74
+ .ame-pv-inline-reset {
75
+ line-height: 28px;
76
+ margin-left: 1em;
77
+ }
78
+
79
+ td {
80
+ box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1);
81
+ }
82
+
83
+ p.submit {
84
+ margin-bottom: 0.2em;
85
+ }
86
+ }
modules/plugin-visibility/plugin-visibility.ts CHANGED
@@ -3,9 +3,10 @@
3
  /// <reference path="../../js/jqueryui.d.ts" />
4
  /// <reference path="../../js/lodash-3.10.d.ts" />
5
  /// <reference path="../../modules/actor-selector/actor-selector.ts" />
 
6
 
7
- declare var amePluginVisibility: AmePluginVisibilityModule;
8
- declare var wsPluginVisibilityData: PluginVisibilityScriptData;
9
 
10
  interface PluginVisibilityScriptData {
11
  isMultisite: boolean,
@@ -13,10 +14,7 @@ interface PluginVisibilityScriptData {
13
  selectedActor: string,
14
  installedPlugins: Array<PvPluginInfo>,
15
  settings: PluginVisibilitySettings,
16
- isProVersion: boolean,
17
-
18
- adminAjaxUrl: string,
19
- dismissNoticeNonce: string
20
  }
21
 
22
  interface PluginVisibilitySettings {
@@ -24,7 +22,9 @@ interface PluginVisibilitySettings {
24
  plugins: {
25
  [fileName : string] : {
26
  isVisibleByDefault: boolean,
27
- grantAccess: GrantAccessMap
 
 
28
  }
29
  }
30
  }
@@ -37,7 +37,10 @@ interface PvPluginInfo {
37
  name: string,
38
  fileName: string,
39
  description: string,
40
- isActive: boolean
 
 
 
41
  }
42
 
43
  class AmePluginVisibilityModule {
@@ -66,7 +69,7 @@ class AmePluginVisibilityModule {
66
  this.actorSelector = new AmeActorSelector(AmeActors, scriptData.isProVersion);
67
 
68
  //Wrap the selected actor in a computed observable so that it can be used with Knockout.
69
- var _selectedActor = ko.observable(this.actorSelector.selectedActor);
70
  this.selectedActor = ko.computed<string>({
71
  read: function () {
72
  return _selectedActor();
@@ -95,6 +98,10 @@ class AmePluginVisibilityModule {
95
  this.plugins = _.map(scriptData.installedPlugins, (plugin) => {
96
  return new AmePlugin(plugin, _.get(scriptData.settings.plugins, plugin.fileName, {}), this);
97
  });
 
 
 
 
98
 
99
  this.privilegedActors = [this.actorSelector.getCurrentUserActor()];
100
  if (this.isMultisite) {
@@ -202,13 +209,16 @@ class AmePluginVisibilityModule {
202
  isVisibleByDefault: plugin.isVisibleByDefault(),
203
  grantAccess: _.mapValues(plugin.grantAccess, (allow): boolean => {
204
  return allow();
205
- })
 
 
206
  };
207
  });
208
 
209
  return result;
210
  }
211
 
 
212
  saveChanges() {
213
  const settings = this.getSettings();
214
 
@@ -226,28 +236,60 @@ class AmePluginVisibilityModule {
226
  }
227
  }
228
 
229
- class AmePlugin implements PvPluginInfo {
230
- name: string;
231
  fileName: string;
232
- description: string;
233
  isActive: boolean;
234
 
 
 
 
 
 
 
 
 
 
235
  isChecked: KnockoutComputed<boolean>;
236
 
237
  isVisibleByDefault: KnockoutObservable<boolean>;
238
  grantAccess: {[actorId : string] : KnockoutObservable<boolean>};
239
 
240
- constructor(details: PvPluginInfo, visibility: Object, module: AmePluginVisibilityModule) {
241
- this.name = AmePlugin.stripAllTags(details.name);
242
- this.description = AmePlugin.stripAllTags(details.description);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  this.fileName = details.fileName;
244
  this.isActive = details.isActive;
245
 
246
- const _ = AmePluginVisibilityModule._;
247
- this.isVisibleByDefault = ko.observable(_.get(visibility, 'isVisibleByDefault', true));
 
 
 
248
 
249
  const emptyGrant: {[actorId : string] : boolean} = {};
250
- this.grantAccess = _.mapValues(_.get(visibility, 'grantAccess', emptyGrant), (hasAccess) => {
251
  return ko.observable<boolean>(hasAccess);
252
  });
253
 
@@ -268,9 +310,43 @@ class AmePlugin implements PvPluginInfo {
268
  return this.grantAccess[actorId];
269
  }
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  static stripAllTags(input): string {
272
  //Based on: http://phpjs.org/functions/strip_tags/
273
- var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
274
  commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
275
  return input.replace(commentsAndPhpTags, '').replace(tags, '');
276
  }
@@ -282,12 +358,6 @@ jQuery(function ($) {
282
 
283
  //Permanently dismiss the usage hint via AJAX.
284
  $('#ame-pv-usage-notice').on('click', '.notice-dismiss', function() {
285
- $.post(
286
- wsPluginVisibilityData.adminAjaxUrl,
287
- {
288
- 'action' : 'ws_ame_dismiss_pv_usage_notice',
289
- '_ajax_nonce' : wsPluginVisibilityData.dismissNoticeNonce
290
- }
291
- );
292
  });
293
  });
3
  /// <reference path="../../js/jqueryui.d.ts" />
4
  /// <reference path="../../js/lodash-3.10.d.ts" />
5
  /// <reference path="../../modules/actor-selector/actor-selector.ts" />
6
+ /// <reference path="../../ajax-wrapper/ajax-action-wrapper.d.ts" />
7
 
8
+ declare let amePluginVisibility: AmePluginVisibilityModule;
9
+ declare const wsPluginVisibilityData: PluginVisibilityScriptData;
10
 
11
  interface PluginVisibilityScriptData {
12
  isMultisite: boolean,
14
  selectedActor: string,
15
  installedPlugins: Array<PvPluginInfo>,
16
  settings: PluginVisibilitySettings,
17
+ isProVersion: boolean
 
 
 
18
  }
19
 
20
  interface PluginVisibilitySettings {
22
  plugins: {
23
  [fileName : string] : {
24
  isVisibleByDefault: boolean,
25
+ grantAccess: GrantAccessMap,
26
+ customName: string,
27
+ customDescription: string
28
  }
29
  }
30
  }
37
  name: string,
38
  fileName: string,
39
  description: string,
40
+ isActive: boolean,
41
+
42
+ customName: string,
43
+ customDescription: string
44
  }
45
 
46
  class AmePluginVisibilityModule {
69
  this.actorSelector = new AmeActorSelector(AmeActors, scriptData.isProVersion);
70
 
71
  //Wrap the selected actor in a computed observable so that it can be used with Knockout.
72
+ let _selectedActor = ko.observable(this.actorSelector.selectedActor);
73
  this.selectedActor = ko.computed<string>({
74
  read: function () {
75
  return _selectedActor();
98
  this.plugins = _.map(scriptData.installedPlugins, (plugin) => {
99
  return new AmePlugin(plugin, _.get(scriptData.settings.plugins, plugin.fileName, {}), this);
100
  });
101
+ //Normally, the plugin list is sorted by the (real) plugin name. Re-sort taking custom names into account.
102
+ this.plugins.sort(function(a, b) {
103
+ return a.name().localeCompare(b.name());
104
+ });
105
 
106
  this.privilegedActors = [this.actorSelector.getCurrentUserActor()];
107
  if (this.isMultisite) {
209
  isVisibleByDefault: plugin.isVisibleByDefault(),
210
  grantAccess: _.mapValues(plugin.grantAccess, (allow): boolean => {
211
  return allow();
212
+ }),
213
+ customName: plugin.customName(),
214
+ customDescription: plugin.customDescription()
215
  };
216
  });
217
 
218
  return result;
219
  }
220
 
221
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
222
  saveChanges() {
223
  const settings = this.getSettings();
224
 
236
  }
237
  }
238
 
239
+ class AmePlugin {
240
+ name: KnockoutComputed<string>;
241
  fileName: string;
242
+ description: KnockoutComputed<string>;
243
  isActive: boolean;
244
 
245
+ defaultName: KnockoutObservable<string>;
246
+ defaultDescription: KnockoutObservable<string>;
247
+
248
+ isBeingEdited: KnockoutObservable<boolean>;
249
+ customName: KnockoutObservable<string>;
250
+ customDescription: KnockoutObservable<string>;
251
+ editableName: KnockoutObservable<string>;
252
+ editableDescription: KnockoutObservable<string>;
253
+
254
  isChecked: KnockoutComputed<boolean>;
255
 
256
  isVisibleByDefault: KnockoutObservable<boolean>;
257
  grantAccess: {[actorId : string] : KnockoutObservable<boolean>};
258
 
259
+ constructor(details: PvPluginInfo, settings: Object, module: AmePluginVisibilityModule) {
260
+ const _ = AmePluginVisibilityModule._;
261
+
262
+ this.defaultName = ko.observable(details.name);
263
+ this.defaultDescription = ko.observable(details.description);
264
+ this.customName = ko.observable(_.get(settings, 'customName', ''));
265
+ this.customDescription = ko.observable(_.get(settings, 'customDescription', ''));
266
+
267
+ this.name = ko.computed(() => {
268
+ let value = this.customName();
269
+ if (value === '') {
270
+ value = this.defaultName();
271
+ }
272
+ return AmePlugin.stripAllTags(value);
273
+ });
274
+ this.description = ko.computed(() => {
275
+ let value = this.customDescription();
276
+ if (value === '') {
277
+ value = this.defaultDescription();
278
+ }
279
+ return AmePlugin.stripAllTags(value);
280
+ });
281
+
282
  this.fileName = details.fileName;
283
  this.isActive = details.isActive;
284
 
285
+ this.isBeingEdited = ko.observable(false);
286
+ this.editableName = ko.observable(this.defaultName());
287
+ this.editableDescription = ko.observable(this.defaultDescription());
288
+
289
+ this.isVisibleByDefault = ko.observable(_.get(settings, 'isVisibleByDefault', true));
290
 
291
  const emptyGrant: {[actorId : string] : boolean} = {};
292
+ this.grantAccess = _.mapValues(_.get(settings, 'grantAccess', emptyGrant), (hasAccess) => {
293
  return ko.observable<boolean>(hasAccess);
294
  });
295
 
310
  return this.grantAccess[actorId];
311
  }
312
 
313
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
314
+ openInlineEditor() {
315
+ this.editableName(this.customName() === '' ? this.defaultName() : this.customName());
316
+ this.editableDescription(this.customDescription() === '' ? this.defaultDescription() : this.customDescription());
317
+ this.isBeingEdited(true);
318
+ }
319
+
320
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
321
+ cancelEdit() {
322
+ this.isBeingEdited(false);
323
+ }
324
+
325
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
326
+ confirmEdit() {
327
+ this.customName(this.editableName());
328
+ this.customDescription(this.editableDescription());
329
+
330
+ if (this.customName() === this.defaultName()) {
331
+ this.customName('');
332
+ }
333
+ if (this.customDescription() === this.defaultDescription()) {
334
+ this.customDescription('');
335
+ }
336
+
337
+ this.isBeingEdited(false);
338
+ }
339
+
340
+ //noinspection JSUnusedGlobalSymbols Used in KO template.
341
+ resetNameAndDescription() {
342
+ this.customName('');
343
+ this.customDescription('');
344
+ this.isBeingEdited(false);
345
+ }
346
+
347
  static stripAllTags(input): string {
348
  //Based on: http://phpjs.org/functions/strip_tags/
349
+ const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
350
  commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
351
  return input.replace(commentsAndPhpTags, '').replace(tags, '');
352
  }
358
 
359
  //Permanently dismiss the usage hint via AJAX.
360
  $('#ame-pv-usage-notice').on('click', '.notice-dismiss', function() {
361
+ AjawV1.getAction('ws_ame_dismiss_pv_usage_notice').request();
 
 
 
 
 
 
362
  });
363
  });
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: whiteshadow
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
  Requires at least: 4.1
6
- Tested up to: 4.7
7
- Stable tag: 1.7.3
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
@@ -63,6 +63,20 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  = 1.7.3 =
67
  * Fixed a bug where closing the menu properties of a custom menu item could set "extra capability" to "read".
68
  * Added a workaround for WooCommerce 2.6.8 to display the number of new orders in the "Orders" menu title.
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
  Requires at least: 4.1
6
+ Tested up to: 4.8
7
+ Stable tag: 1.8
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
63
 
64
  == Changelog ==
65
 
66
+ = 1.8 =
67
+ * You can edit plugin names and descriptions through the "Plugins" tab. This only changes how plugins are displayed on the "Plugins" page. It doesn't affect plugin files on disk.
68
+ * Added an option to highlight new menu items. This feature is off by default. You can enable it in the "Settings" tab.
69
+ * Added an option to compress menu data that the plugin stores in the database.
70
+ * Added a compatibility workaround for the Divi Training plugin. The hidden menu items that it adds to the "Dashboard" menu should no longer show up when you activate AME.
71
+ * Added a workaround that improves compatibility with plugins that set their menu icons using CSS.
72
+ * Fixed an old bug where sorting menu items would put all separators at the top. Now they'll stay near their preceding menu item.
73
+ * Fixed incorrect shadows on custom screen options links.
74
+ * Fixed a couple of UI layout issues that were caused by bugs in other plugins.
75
+ * Fixed a rare issue where hiding the admin bar would leave behind empty space.
76
+ * When you use the "A-Z" button to sort top level menus, it also sorts submenu items. To avoid compatibility issues, the first item of each submenu stays in its original position.
77
+ * Automatically reset plugin access if the only allowed user no longer exists. This should cut down on the number of users who accidentally lock themselves out by setting "Who can access the plugin" to "Only the current user" and then later deleting that user account.
78
+ * Minor performance optimizations.
79
+
80
  = 1.7.3 =
81
  * Fixed a bug where closing the menu properties of a custom menu item could set "extra capability" to "read".
82
  * Added a workaround for WooCommerce 2.6.8 to display the number of new orders in the "Orders" menu title.