Admin Menu Editor - Version 1.8.3

Version Description

  • Added a couple of tutorial links to the settings page.
  • Fixed a potential crash that was caused by a bug in the "WP Editor" plugin version 1.2.6.3.
  • Fixed some obsolete callback syntax that was still using "&$this".
  • Changed the order of some menu settings and added separators between groups of settings.
  • Removed the "Screen Options" panel from AME tabs that didn't need it like "Plugins".
  • Tested with WP 4.9.5.
Download this release

Release Info

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

Code changes from version 1.8.2 to 1.8.3

css/_test-access-screen.scss ADDED
@@ -0,0 +1,131 @@
1
+ /*********************************************
2
+ "Test access" dialog
3
+ **********************************************/
4
+
5
+ #ws_ame_test_access_screen {
6
+ display: none;
7
+ background: #fcfcfc;
8
+ }
9
+
10
+ #ws_ame_test_inputs {
11
+ //border-bottom: 1px solid #ddd;
12
+ padding-bottom: 16px;
13
+ }
14
+
15
+ .ws_ame_test_input {
16
+ display: block;
17
+ float: left;
18
+
19
+ width: 100%;
20
+ margin: 2px 0;
21
+ box-sizing: content-box;
22
+ }
23
+
24
+ .ws_ame_test_input_name {
25
+ display: block;
26
+ float: left;
27
+ width: 35%;
28
+ margin-right: 4%;
29
+
30
+ text-align: right;
31
+ padding-top: 6px;
32
+ line-height: 16px;
33
+ }
34
+
35
+ .ws_ame_test_input_value {
36
+ display: block;
37
+ float: right;
38
+ width: 60%;
39
+
40
+ -webkit-box-sizing: border-box;
41
+ -moz-box-sizing: border-box;
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ #ws_ame_test_actions {
46
+ float: left;
47
+ width: 100%;
48
+ margin-top: 1em;
49
+ }
50
+
51
+ #ws_ame_test_button_container {
52
+ width: 35%;
53
+ margin-right: 4%;
54
+ float: left;
55
+ text-align: right;
56
+ }
57
+
58
+ #ws_ame_test_progress {
59
+ display: none;
60
+ width: 60%;
61
+ float: right;
62
+
63
+ .spinner {
64
+ float: none;
65
+ vertical-align: bottom;
66
+ margin-left: 0;
67
+ margin-right: 4px;
68
+ }
69
+ }
70
+
71
+ #ws_ame_test_access_body {
72
+ width: 100%;
73
+ position: relative;
74
+
75
+ border: 1px solid #ddd;
76
+ -webkit-border-radius: 3px;
77
+ -moz-border-radius: 3px;
78
+ border-radius: 3px;
79
+ }
80
+
81
+ #ws_ame_test_frame_container {
82
+ margin-right: 250px;
83
+ background: white;
84
+
85
+ min-height: 500px;
86
+ position: relative;
87
+ }
88
+
89
+ #ws_ame_test_access_frame {
90
+ -webkit-box-sizing: border-box;
91
+ -moz-box-sizing: border-box;
92
+ box-sizing: border-box;
93
+
94
+ width: 100%;
95
+ height: 100%;
96
+ min-height: 500px;
97
+
98
+ border: none;
99
+ margin: 0;
100
+ padding: 0;
101
+ }
102
+
103
+ #ws_ame_test_access_sidebar {
104
+ -webkit-box-sizing: border-box;
105
+ -moz-box-sizing: border-box;
106
+ box-sizing: border-box;
107
+
108
+ position: absolute;
109
+ top: 0;
110
+ right: 0;
111
+ bottom: 0;
112
+
113
+ width: 250px;
114
+ padding: 16px 24px;
115
+
116
+ background-color: #f3f3f3;
117
+ border-left: 1px solid #ddd;
118
+
119
+ h4:first-of-type {
120
+ margin-top: 0;
121
+ }
122
+ }
123
+
124
+ #ws_ame_test_frame_placeholder {
125
+ display: block;
126
+ padding: 16px 24px;
127
+ }
128
+
129
+ #ws_ame_test_output {
130
+ display: none;
131
+ }
css/menu-editor.css CHANGED
@@ -272,6 +272,19 @@
272
height: 25px;
273
min-height: 25px; }
274
275
/* The reset-to-default button */
276
.ws_reset_button {
277
display: block;
@@ -805,16 +818,20 @@ a#ws-ame-delete-color-preset:hover {
805
/* Color scheme display in the editor widget. */
806
.ws_color_scheme_display {
807
display: inline-block;
808
- height: 20px;
809
- width: 186px;
810
margin-right: 5px;
811
- padding: 2px 3px;
812
font-size: 12px;
813
border: 1px solid #ddd;
814
background: white;
815
cursor: pointer;
816
line-height: 20px; }
817
818
.ws_color_display_item {
819
display: inline-block;
820
width: 18px;
@@ -956,7 +973,8 @@ a#ws-ame-delete-color-preset:hover {
956
margin-right: 5px; }
957
958
.ws_launch_access_editor {
959
- min-width: 40px; }
960
961
#ws_menu_access_editor {
962
width: 400px;
@@ -1408,6 +1426,22 @@ a#ws-ame-delete-color-preset:hover {
1408
margin-top: 0;
1409
margin-bottom: 1em; }
1410
1411
/*********************************************
1412
Miscellaneous
1413
**********************************************/
@@ -1441,4 +1475,106 @@ a#ws-ame-delete-color-preset:hover {
1441
.test-content {
1442
padding: 8px; }
1443
1444
/*# sourceMappingURL=menu-editor.css.map */
272
height: 25px;
273
min-height: 25px; }
274
275
+ /*
276
+ * Group headings
277
+ */
278
+ .ws_edit_field.ws_field_group_heading {
279
+ height: 1px;
280
+ min-height: 0;
281
+ padding-top: 0;
282
+ background: #ccc;
283
+ margin: 8px -4px 5px; }
284
+ .ws_edit_field.ws_field_group_heading span {
285
+ display: none;
286
+ font-weight: bold; }
287
+
288
/* The reset-to-default button */
289
.ws_reset_button {
290
display: block;
818
/* Color scheme display in the editor widget. */
819
.ws_color_scheme_display {
820
display: inline-block;
821
+ box-sizing: border-box;
822
+ height: 26px;
823
+ width: 190px;
824
margin-right: 5px;
825
+ padding: 2px 4px;
826
font-size: 12px;
827
border: 1px solid #ddd;
828
background: white;
829
cursor: pointer;
830
line-height: 20px; }
831
832
+ .ws_open_color_editor {
833
+ width: 58px; }
834
+
835
.ws_color_display_item {
836
display: inline-block;
837
width: 18px;
973
margin-right: 5px; }
974
975
.ws_launch_access_editor {
976
+ min-width: 40px;
977
+ width: 58px; }
978
979
#ws_menu_access_editor {
980
width: 400px;
1426
margin-top: 0;
1427
margin-bottom: 1em; }
1428
1429
+ .ame-available-add-ons tr:first-of-type td {
1430
+ margin-top: 0;
1431
+ padding-top: 0; }
1432
+ .ame-available-add-ons td {
1433
+ padding-top: 10px;
1434
+ padding-bottom: 10px; }
1435
+ .ame-available-add-ons .ame-add-on-heading {
1436
+ padding-left: 0; }
1437
+
1438
+ .ame-add-on-name {
1439
+ font-weight: 600; }
1440
+
1441
+ .ame-add-on-details-link::after {
1442
+ /*content: " \f504";
1443
+ font-family: dashicons, sans-serif;*/ }
1444
+
1445
/*********************************************
1446
Miscellaneous
1447
**********************************************/
1475
.test-content {
1476
padding: 8px; }
1477
1478
+ /*********************************************
1479
+ "Test access" dialog
1480
+ **********************************************/
1481
+ #ws_ame_test_access_screen {
1482
+ display: none;
1483
+ background: #fcfcfc; }
1484
+
1485
+ #ws_ame_test_inputs {
1486
+ padding-bottom: 16px; }
1487
+
1488
+ .ws_ame_test_input {
1489
+ display: block;
1490
+ float: left;
1491
+ width: 100%;
1492
+ margin: 2px 0;
1493
+ box-sizing: content-box; }
1494
+
1495
+ .ws_ame_test_input_name {
1496
+ display: block;
1497
+ float: left;
1498
+ width: 35%;
1499
+ margin-right: 4%;
1500
+ text-align: right;
1501
+ padding-top: 6px;
1502
+ line-height: 16px; }
1503
+
1504
+ .ws_ame_test_input_value {
1505
+ display: block;
1506
+ float: right;
1507
+ width: 60%;
1508
+ -webkit-box-sizing: border-box;
1509
+ -moz-box-sizing: border-box;
1510
+ box-sizing: border-box; }
1511
+
1512
+ #ws_ame_test_actions {
1513
+ float: left;
1514
+ width: 100%;
1515
+ margin-top: 1em; }
1516
+
1517
+ #ws_ame_test_button_container {
1518
+ width: 35%;
1519
+ margin-right: 4%;
1520
+ float: left;
1521
+ text-align: right; }
1522
+
1523
+ #ws_ame_test_progress {
1524
+ display: none;
1525
+ width: 60%;
1526
+ float: right; }
1527
+ #ws_ame_test_progress .spinner {
1528
+ float: none;
1529
+ vertical-align: bottom;
1530
+ margin-left: 0;
1531
+ margin-right: 4px; }
1532
+
1533
+ #ws_ame_test_access_body {
1534
+ width: 100%;
1535
+ position: relative;
1536
+ border: 1px solid #ddd;
1537
+ -webkit-border-radius: 3px;
1538
+ -moz-border-radius: 3px;
1539
+ border-radius: 3px; }
1540
+
1541
+ #ws_ame_test_frame_container {
1542
+ margin-right: 250px;
1543
+ background: white;
1544
+ min-height: 500px;
1545
+ position: relative; }
1546
+
1547
+ #ws_ame_test_access_frame {
1548
+ -webkit-box-sizing: border-box;
1549
+ -moz-box-sizing: border-box;
1550
+ box-sizing: border-box;
1551
+ width: 100%;
1552
+ height: 100%;
1553
+ min-height: 500px;
1554
+ border: none;
1555
+ margin: 0;
1556
+ padding: 0; }
1557
+
1558
+ #ws_ame_test_access_sidebar {
1559
+ -webkit-box-sizing: border-box;
1560
+ -moz-box-sizing: border-box;
1561
+ box-sizing: border-box;
1562
+ position: absolute;
1563
+ top: 0;
1564
+ right: 0;
1565
+ bottom: 0;
1566
+ width: 250px;
1567
+ padding: 16px 24px;
1568
+ background-color: #f3f3f3;
1569
+ border-left: 1px solid #ddd; }
1570
+ #ws_ame_test_access_sidebar h4:first-of-type {
1571
+ margin-top: 0; }
1572
+
1573
+ #ws_ame_test_frame_placeholder {
1574
+ display: block;
1575
+ padding: 16px 24px; }
1576
+
1577
+ #ws_ame_test_output {
1578
+ display: none; }
1579
+
1580
/*# sourceMappingURL=menu-editor.css.map */
css/menu-editor.scss CHANGED
@@ -393,6 +393,24 @@ $mainContainerBorderRadius: 0px;
393
min-height: 25px;
394
}
395
396
/* The reset-to-default button */
397
.ws_reset_button {
398
display: block;
@@ -427,11 +445,12 @@ $mainContainerBorderRadius: 0px;
427
}
428
429
/* The input box in each field editor */
430
#ws_menu_editor .ws_editbox input[type="text"],
431
#ws_menu_editor .ws_editbox select {
432
display: block;
433
float: left;
434
- width: 254px;
435
height: 25px;
436
437
font-size: 12px;
@@ -1088,22 +1107,30 @@ a#ws-ame-delete-color-preset:hover {
1088
1089
/* Color scheme display in the editor widget. */
1090
1091
.ws_color_scheme_display {
1092
- $colorFieldHeight: 20px;
1093
1094
display: inline-block;
1095
height: $colorFieldHeight;
1096
- width: 186px;
1097
1098
- margin-right: 5px;
1099
- padding: 2px 3px;
1100
1101
font-size: 12px;
1102
border: 1px solid #ddd;
1103
background: white;
1104
cursor: pointer;
1105
1106
- line-height: $colorFieldHeight;
1107
}
1108
1109
.ws_color_display_item {
@@ -1285,14 +1312,17 @@ a#ws-ame-delete-color-preset:hover {
1285
*************************************/
1286
1287
/* The launch button */
1288
#ws_menu_editor .ws_edit_field-access_level input.ws_field_value
1289
{
1290
- width: 190px;
1291
- margin-right: 5px;
1292
}
1293
1294
.ws_launch_access_editor {
1295
min-width: 40px;
1296
}
1297
1298
#ws_menu_access_editor {
@@ -1991,6 +2021,31 @@ $userSelectionPanelPadding: 10px;
1991
margin-bottom: 1em;
1992
}
1993
1994
/*********************************************
1995
Miscellaneous
1996
**********************************************/
@@ -2034,4 +2089,6 @@ $userSelectionPanelPadding: 10px;
2034
2035
.test-content {
2036
padding: 8px;
2037
- }
393
min-height: 25px;
394
}
395
396
+ /*
397
+ * Group headings
398
+ */
399
+ .ws_edit_field.ws_field_group_heading {
400
+ //display: none;
401
+ height: 1px;
402
+ min-height: 0;
403
+ padding-top: 0;
404
+
405
+ background: #ccc;
406
+ margin: 8px -4px 5px;
407
+
408
+ & span {
409
+ display: none;
410
+ font-weight: bold;
411
+ }
412
+ }
413
+
414
/* The reset-to-default button */
415
.ws_reset_button {
416
display: block;
445
}
446
447
/* The input box in each field editor */
448
+ $basicInputWidth: 254px;
449
#ws_menu_editor .ws_editbox input[type="text"],
450
#ws_menu_editor .ws_editbox select {
451
display: block;
452
float: left;
453
+ width: $basicInputWidth;
454
height: 25px;
455
456
font-size: 12px;
1107
1108
/* Color scheme display in the editor widget. */
1109
1110
+ $colorFieldWidth: 190px;
1111
+ $colorFieldRightMargin: 5px;
1112
+
1113
.ws_color_scheme_display {
1114
+ $colorFieldHeight: 26px;
1115
1116
display: inline-block;
1117
+ box-sizing: border-box;
1118
height: $colorFieldHeight;
1119
+ width: $colorFieldWidth;
1120
1121
+ margin-right: $colorFieldRightMargin;
1122
+ padding: 2px 4px;
1123
1124
font-size: 12px;
1125
border: 1px solid #ddd;
1126
background: white;
1127
cursor: pointer;
1128
1129
+ line-height: $colorFieldHeight - 6px;
1130
+ }
1131
+
1132
+ .ws_open_color_editor {
1133
+ width: $basicInputWidth - $colorFieldWidth - $colorFieldRightMargin - 1px;
1134
}
1135
1136
.ws_color_display_item {
1312
*************************************/
1313
1314
/* The launch button */
1315
+ $accessLevelInputWidth: 190px;
1316
+ $accessLevelRightMargin: 5px;
1317
#ws_menu_editor .ws_edit_field-access_level input.ws_field_value
1318
{
1319
+ width: $accessLevelInputWidth;
1320
+ margin-right: $accessLevelRightMargin;
1321
}
1322
1323
.ws_launch_access_editor {
1324
min-width: 40px;
1325
+ width: $basicInputWidth - $accessLevelRightMargin - $accessLevelInputWidth - 1px;
1326
}
1327
1328
#ws_menu_access_editor {
2021
margin-bottom: 1em;
2022
}
2023
2024
+ .ame-available-add-ons {
2025
+ tr:first-of-type td {
2026
+ margin-top: 0;
2027
+ padding-top: 0;
2028
+ }
2029
+
2030
+ td {
2031
+ padding-top: 10px;
2032
+ padding-bottom: 10px;
2033
+ }
2034
+
2035
+ .ame-add-on-heading {
2036
+ padding-left: 0;
2037
+ }
2038
+ }
2039
+
2040
+ .ame-add-on-name {
2041
+ font-weight: 600;
2042
+ }
2043
+
2044
+ .ame-add-on-details-link::after {
2045
+ /*content: " \f504";
2046
+ font-family: dashicons, sans-serif;*/
2047
+ }
2048
+
2049
/*********************************************
2050
Miscellaneous
2051
**********************************************/
2089
2090
.test-content {
2091
padding: 8px;
2092
+ }
2093
+
2094
+ @import "test-access-screen";
includes/access-test-runner.php ADDED
@@ -0,0 +1,268 @@
1
+ <?php
2
+
3
+ class ameAccessTestRunner implements ArrayAccess {
4
+ const TEST_DATA_META_KEY = 'ws_ame_access_test_data';
5
+
6
+ /**
7
+ * @var WPMenuEditor
8
+ */
9
+ private $menuEditor;
10
+
11
+ private $get = array();
12
+
13
+ private $test_menu = null;
14
+ private $test_target_item = null;
15
+ private $test_target_parent = null;
16
+ private $test_relevant_role = null;
17
+
18
+ private $original_wp_die_handler = null;
19
+ private $access_test_results = array();
20
+
21
+ public function __construct($menuEditor, $queryParameters) {
22
+ $this->menuEditor = $menuEditor;
23
+ $this->get = $queryParameters;
24
+
25
+ add_filter('admin_menu_editor-script_data', array($this, 'addEditorScriptData'));
26
+
27
+ add_action('wp_ajax_ws_ame_set_test_configuration', array($this, 'ajax_set_test_configuration'));
28
+ add_action('set_current_user', array($this, 'init_access_test'));
29
+ }
30
+
31
+ public function addEditorScriptData($scriptData) {
32
+ $scriptData = array_merge(
33
+ $scriptData,
34
+ array(
35
+ 'setTestConfigurationNonce' => wp_create_nonce('ws_ame_set_test_configuration'),
36
+ 'testAccessNonce' => wp_create_nonce('ws_ame_test_access'),
37
+ )
38
+ );
39
+ return $scriptData;
40
+ }
41
+
42
+ public function ajax_set_test_configuration() {
43
+ check_ajax_referer('ws_ame_set_test_configuration');
44
+ if ( !$this->menuEditor->current_user_can_edit_menu() ) {
45
+ exit($this->menuEditor->json_encode(array(
46
+ 'error' => 'You don\'t have permission to test menu settings.',
47
+ )));
48
+ }
49
+
50
+ $post = $this->menuEditor->get_post_params();
51
+ $menuData = strval($post['data']);
52
+
53
+ $metaId = add_user_meta(get_current_user_id(), self::TEST_DATA_META_KEY, wp_slash($menuData), false);
54
+ if ( $metaId === false ) {
55
+ exit($this->menuEditor->json_encode(array(
56
+ 'error' => 'Failed to store test data. add_user_meta() returned FALSE.',
57
+ )));
58
+ }
59
+
60
+ exit($this->menuEditor->json_encode(array(
61
+ 'success' => true,
62
+ 'meta_id' => $metaId,
63
+ )));
64
+ }
65
+
66
+ public function init_access_test() {
67
+ //We want to do this only once per page load: specifically, when WP authenticates
68
+ //the user at the start of the request.
69
+ static $is_user_already_set = false;
70
+ if ( $is_user_already_set || $this->menuEditor->is_access_test || did_action('init') ) {
71
+ return;
72
+ }
73
+ $is_user_already_set = true;
74
+
75
+ if (
76
+ !isset(
77
+ $this->get['ame-test-menu-access-as'],
78
+ $this->get['ame-test-target-item']
79
+ )
80
+ || !check_admin_referer('ws_ame_test_access')
81
+ ) {
82
+ return;
83
+ }
84
+
85
+
86
+ $configurations = get_user_meta(get_current_user_id(), self::TEST_DATA_META_KEY, false);
87
+ if ( empty($configurations) ) {
88
+ exit('Error: Test data not found.');
89
+ }
90
+
91
+ //Use the most recent config. It's usually the last one.
92
+ $json = array_pop($configurations);
93
+ //Clean up the database.
94
+ delete_user_meta(get_current_user_id(), self::TEST_DATA_META_KEY, wp_slash($json));
95
+
96
+ try {
97
+ $test_menu = ameMenu::load_json($json);
98
+ } catch (InvalidMenuException $e) {
99
+ exit($e->getMessage());
100
+ }
101
+ $this->test_menu = $test_menu;
102
+
103
+ $user = get_user_by('login', strval($this->get['ame-test-menu-access-as']));
104
+ if ( !$user ) {
105
+ exit('Error: User not found.');
106
+ }
107
+
108
+ //Everything looks good, proceed with the test.
109
+ $this->menuEditor->is_access_test = true;
110
+
111
+ $this->access_test_results = array();
112
+ $this->test_target_item = strval($this->get['ame-test-target-item']);
113
+ $this->test_target_parent = ameUtils::get($this->get, 'ame-test-target-parent', null);
114
+ $this->test_relevant_role = ameUtils::get($this->get, 'ame-test-relevant-role', null);
115
+
116
+ if ( $this->test_target_parent === '' ) {
117
+ $this->test_target_parent = null;
118
+ }
119
+ if ( $this->test_relevant_role === null ) {
120
+ $this->test_relevant_role = null;
121
+ }
122
+
123
+ wp_set_current_user($user->ID, $user->user_login);
124
+
125
+ $this->menuEditor->set_plugin_option('security_logging_enabled', true);
126
+ add_action('admin_print_scripts', array($this, 'output_access_test_results'));
127
+ add_filter('wp_die_handler', array($this, 'replace_die_handler_for_access_test'), 25, 1);
128
+ }
129
+
130
+ public function output_access_test_results() {
131
+ echo $this->get_access_test_result_script();
132
+ }
133
+
134
+ private function get_access_test_result_script() {
135
+ $response = array_merge(
136
+ $this->access_test_results,
137
+ array(
138
+ 'securityLog' => $this->menuEditor->get_security_log(),
139
+ )
140
+ );
141
+
142
+ $script = '<script type="text/javascript">
143
+ window.parent.postMessage((' . $this->menuEditor->json_encode($response) . '), "*");
144
+ </script>';
145
+ return $script;
146
+ }
147
+
148
+ public function replace_die_handler_for_access_test($callback = null) {
149
+ $this->original_wp_die_handler = $callback;
150
+ return array($this, 'die_during_an_access_test');
151
+ }
152
+
153
+ public function die_during_an_access_test($message, $title = '', $args = array()) {
154
+ if ( $this->original_wp_die_handler ) {
155
+ $script = $this->get_access_test_result_script();
156
+ if ( $message instanceof WP_Error ) {
157
+ $message->add('ame-access-test-response', '[Access test]' . $script);
158
+ } else if ( is_string($message) ) {
159
+ $message .= $script;
160
+ }
161
+
162
+ call_user_func($this->original_wp_die_handler, $message, $title, $args);
163
+ } else {
164
+ exit('Unexpected error: wp_die() was called but there is no default handler.');
165
+ }
166
+ }
167
+
168
+ private function find_target_menu_item($items, $item_file, $parent_file = null, $current_parent = null) {
169
+ foreach ($items as $item) {
170
+ $this_file = ameMenuItem::get($item, 'file', null);
171
+ if ( ($this_file === $item_file) && ($parent_file === $current_parent) ) {
172
+ return $item;
173
+ }
174
+
175
+ if ( !empty($item['items']) ) {
176
+ $result = $this->find_target_menu_item($item['items'], $item_file, $parent_file, $this_file);
177
+ if ( $result !== null ) {
178
+ return $result;
179
+ }
180
+ }
181
+ }
182
+ return null;
183
+ }
184
+
185
+ public function setCurrentMenuItem($menuItem) {
186
+ $this->access_test_results['currentMenuItem'] = $menuItem;
187
+
188
+ $this->access_test_results['currentMenuItemIsTarget'] =
189
+ isset($this->access_test_results['currentMenuItem'])
190
+ && (ameMenuItem::get($this->access_test_results['currentMenuItem'], 'file', null) === $this->test_target_item)
191
+ && (ameMenuItem::get($this->access_test_results['currentMenuItem'], 'parent', null) === $this->test_target_parent);
192
+
193
+ $this->access_test_results['isIdentity'] =
194
+ ($this->access_test_results['currentMenuItem'] === $this->access_test_results['targetMenuItem']);
195
+ }
196
+
197
+ public function onFinalTreeReady($tree) {
198
+ //Find the target item. It might not be the same as the current item.
199
+ $this->access_test_results['targetMenuItem'] = $this->find_target_menu_item(
200
+ $tree,
201
+ $this->test_target_item,
202
+ $this->test_target_parent
203
+ );
204
+ }
205
+
206
+
207
+ /**
208
+ * Whether a offset exists
209
+ *
210
+ * @link http://php.net/manual/en/arrayaccess.offsetexists.php
211
+ * @param mixed $offset <p>
212
+ * An offset to check for.
213
+ * </p>
214
+ * @return boolean true on success or false on failure.
215
+ * </p>
216
+ * <p>
217
+ * The return value will be casted to boolean if non-boolean was returned.
218
+ * @since 5.0.0
219
+ */
220
+ public function offsetExists($offset) {
221
+ return array_key_exists($offset, $this->access_test_results);
222
+ }
223
+
224
+ /**
225
+ * Offset to retrieve
226
+ *
227
+ * @link http://php.net/manual/en/arrayaccess.offsetget.php
228
+ * @param mixed $offset <p>
229
+ * The offset to retrieve.
230
+ * </p>
231
+ * @return mixed Can return all value types.
232
+ * @since 5.0.0
233
+ */
234
+ public function offsetGet($offset) {
235
+ return $this->access_test_results[$offset];
236
+ }
237
+
238
+ /**
239
+ * Offset to set
240
+ *
241
+ * @link http://php.net/manual/en/arrayaccess.offsetset.php
242
+ * @param mixed $offset <p>
243
+ * The offset to assign the value to.
244
+ * </p>
245
+ * @param mixed $value <p>
246
+ * The value to set.
247
+ * </p>
248
+ * @return void
249
+ * @since 5.0.0
250
+ */
251
+ public function offsetSet($offset, $value) {
252
+ $this->access_test_results[$offset] = $value;
253
+ }
254
+
255
+ /**
256
+ * Offset to unset
257
+ *
258
+ * @link http://php.net/manual/en/arrayaccess.offsetunset.php
259
+ * @param mixed $offset <p>
260
+ * The offset to unset.
261
+ * </p>
262
+ * @return void
263
+ * @since 5.0.0
264
+ */
265
+ public function offsetUnset($offset) {
266
+ unset($this->access_test_results[$offset]);
267
+ }
268
+ }
includes/editor-page.php CHANGED
@@ -270,6 +270,10 @@ function ame_output_sort_buttons($icons) {
270
<input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
271
<input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
272
273
<?php
274
$compact_layout_title = 'Compact layout';
275
if ( $is_compact_layout_enabled ) {
@@ -309,17 +313,23 @@ function ame_output_sort_buttons($icons) {
309
<?php
310
endif;
311
312
- if ( $is_pro_version ) :
313
- $is_how_to_box_open = true;
314
- if ( isset($_COOKIE['ame_how_to_box_open']) ) {
315
- $is_how_to_box_open = ($_COOKIE['ame_how_to_box_open'] === '1');
316
- }
317
- $box_class = $is_how_to_box_open ? '' : 'closed';
318
319
- $how_to_link_template = '<a href="https://adminmenueditor.com/documentation/%1$s" target="_blank" title="Opens in a new tab">%2$s</a>';
320
- $how_to_item_template = '<li>' . $how_to_link_template . '</li>';
321
322
- ?>
323
<div class="postbox ws_ame_custom_postbox <?php echo $box_class; ?>" id="ws_ame_how_to_box">
324
<button type="button" class="handlediv button-link">
325
<span class="toggle-indicator"></span>
@@ -327,43 +337,58 @@ function ame_output_sort_buttons($icons) {
327
<h2 class="hndle">How To</h2>
328
<div class="inside">
329
<ul class="ame-tutorial-list">
330
- <li><?php
331
- printf($how_to_link_template, 'how-to-hide-a-menu-item/', 'Hide a Menu...');
332
?>
333
- <ul class="ame-tutorial-list">
334
- <?php
335
- foreach (
336
- array(
337
- 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-a-role' => 'From a Role',
338
- 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-a-user' => 'From a User',
339
- 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-everyone-except-yourself' => 'From Everyone Except You',
340
- 'how-to-hide-menu-without-preventing-access/' => 'Without Preventing Access',
341
- )
342
- as $how_to_url => $how_to_title
343
- ) {
344
- printf($how_to_item_template, esc_attr($how_to_url), $how_to_title);
345
- }
346
?>
347
- </ul>
348
- </li>
349
- <?php
350
- foreach (
351
- array(
352
- 'how-to-give-access-to-menu/' => 'Show a Menu',
353
- 'how-to-move-and-sort-menus/' => 'Move and Sort Menus',
354
- 'how-to-add-a-new-menu-item/' => 'Add a New Menu',
355
- )
356
- as $how_to_url => $how_to_title
357
- ) {
358
- printf($how_to_item_template, esc_attr($how_to_url), $how_to_title);
359
- }
360
?>
361
</ul>
362
</div>
363
</div>
364
- <?php
365
- endif;
366
- ?>
367
</div> <!-- / .metabox-holder -->
368
369
<?php
@@ -664,6 +689,8 @@ function ame_output_sort_buttons($icons) {
664
665
<?php include dirname(__FILE__) . '/cap-suggestion-box.php'; ?>
666
667
<?php
668
if ( $is_pro_version ) {
669
include $extrasDirectory . '/page-dropdown.php';
270
<input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
271
<input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
272
273
+ <!--
274
+ <input type="button" id='ws_test_access' value="Test access..." class="button ws_main_button" />
275
+ -->
276
+
277
<?php
278
$compact_layout_title = 'Compact layout';
279
if ( $is_compact_layout_enabled ) {
313
<?php
314
endif;
315
316
+ $is_how_to_box_open = true;
317
+ if ( isset($_COOKIE['ame_how_to_box_open']) ) {
318
+ $is_how_to_box_open = ($_COOKIE['ame_how_to_box_open'] === '1');
319
+ }
320
+ $box_class = $is_how_to_box_open ? '' : 'closed';
321
322
+ if ( $is_pro_version ) {
323
+ $tutorial_base_url = 'https://adminmenueditor.com/documentation/';
324
+ } else {
325
+ $tutorial_base_url = 'https://adminmenueditor.com/free-version-docs/';
326
+ }
327
328
+ /** @noinspection HtmlUnknownTarget */
329
+ $how_to_link_template = '<a href="' . htmlspecialchars($tutorial_base_url) . '%1$s" target="_blank" title="Opens in a new tab">%2$s</a>';
330
+ $how_to_item_template = '<li>' . $how_to_link_template . '</li>';
331
+
332
+ ?>
333
<div class="postbox ws_ame_custom_postbox <?php echo $box_class; ?>" id="ws_ame_how_to_box">
334
<button type="button" class="handlediv button-link">
335
<span class="toggle-indicator"></span>
337
<h2 class="hndle">How To</h2>
338
<div class="inside">
339
<ul class="ame-tutorial-list">
340
+ <?php
341
+ if ( $is_pro_version ):
342
+ //Pro version tutorials.
343
?>
344
+ <li><?php
345
+ printf($how_to_link_template, 'how-to-hide-a-menu-item/', 'Hide a Menu...');
346
?>
347
+ <ul class="ame-tutorial-list">
348
+ <?php
349
+ foreach (
350
+ array(
351
+ 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-a-role' => 'From a Role',
352
+ 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-a-user' => 'From a User',
353
+ 'how-to-hide-a-menu-item/#how-to-hide-a-menu-from-everyone-except-yourself' => 'From Everyone Except You',
354
+ 'how-to-hide-menu-without-preventing-access/' => 'Without Preventing Access',
355
+ )
356
+ as $how_to_url => $how_to_title
357
+ ) {
358
+ printf($how_to_item_template, esc_attr($how_to_url), $how_to_title);
359
+ }
360
+ ?>
361
+ </ul>
362
+ </li>
363
+ <?php
364
+ foreach (
365
+ array(
366
+ 'how-to-give-access-to-menu/' => 'Show a Menu',
367
+ 'how-to-move-and-sort-menus/' => 'Move and Sort Menus',
368
+ 'how-to-add-a-new-menu-item/' => 'Add a New Menu',
369
+ )
370
+ as $how_to_url => $how_to_title
371
+ ) {
372
+ printf($how_to_item_template, esc_attr($how_to_url), $how_to_title);
373
+ }
374
+
375
+ else:
376
+ //Free version tutorials.
377
+ foreach (
378
+ array(
379
+ 'how-to-hide-menus/' => 'Hide a Menu Item',
380
+ 'how-to-hide-menus-cosmetic/' => 'Hide Without Blocking Access',
381
+ 'how-to-add-new-menu/' => 'Add a New Menu',
382
+ )
383
+ as $how_to_url => $how_to_title
384
+ ) {
385
+ printf($how_to_item_template, esc_attr($how_to_url), $how_to_title);
386
+ }
387
+ endif;
388
?>
389
</ul>
390
</div>
391
</div>
392
</div> <!-- / .metabox-holder -->
393
394
<?php
689
690
<?php include dirname(__FILE__) . '/cap-suggestion-box.php'; ?>
691
692
+ <?php include dirname(__FILE__) . '/test-access-screen.php'; ?>
693
+
694
<?php
695
if ( $is_pro_version ) {
696
include $extrasDirectory . '/page-dropdown.php';
includes/menu-editor-core.php CHANGED
@@ -17,6 +17,7 @@ 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 {
22
const WPML_CONTEXT = 'admin-menu-editor menu texts';
@@ -119,6 +120,13 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
119
*/
120
private $caps_used_in_menu = array();
121
122
function init(){
123
$this->sitewide_options = true;
124
@@ -168,6 +176,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
168
//the current site but did not exist on the site where the user last edited the menu configuration.
169
'unused_item_position' => 'relative', //"relative" or "bottom".
170
171
//Verbosity level of menu permission errors.
172
'error_verbosity' => self::VERBOSITY_NORMAL,
173
@@ -293,6 +305,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
293
//Multisite: Clear role and capability caches when switching to another site.
294
add_action('switch_blog', array($this, 'clear_site_specific_caches'), 10, 0);
295
296
//Utility actions. Modules can use them in their templates.
297
add_action('admin_menu_editor-display_tabs', array($this, 'display_editor_tabs'));
298
add_action('admin_menu_editor-display_header', array($this, 'display_settings_page_header'));
@@ -455,7 +473,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
455
if ( is_network_admin() ) {
456
$screen_hook_name .= '-network';
457
}
458
- add_meta_box("ws-ame-screen-options", "[AME placeholder]", '__return_false', $screen_hook_name);
459
}
460
461
//Compatibility fix for the WooCommerce order count bubble. Must be run before storing or processing $submenu.
@@ -508,8 +528,17 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
508
$this->build_custom_wp_menu($this->merged_custom_menu['tree']);
509
$this->user_cap_cache_enabled = false;
510
511
if ( !$this->user_can_access_current_page() ) {
512
$this->log_security_note('DENY access.');
513
$message = 'You do not have sufficient permissions to access this admin page.';
514
515
if ( ($this->options['error_verbosity'] >= self::VERBOSITY_NORMAL) ) {
@@ -532,6 +561,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
532
wp_die($message);
533
} else {
534
$this->log_security_note('ALLOW access.');
535
}
536
537
//Replace the admin menu just before it is displayed and restore it afterwards.
@@ -1032,7 +1065,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1032
return array(
1033
'user_login' => $user->get('user_login'),
1034
'id' => $user->ID,
1035
- 'roles' => !empty($user->roles) ? (array)($user->roles) : array(),
1036
'capabilities' => $this->castValuesToBool($user->caps),
1037
'meta_capabilities' => array(),
1038
'display_name' => $user->display_name,
@@ -1228,6 +1261,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1228
*
1229
* @param null $config_id
1230
* @return array|null Either a menu in the internal format, or NULL if there is no custom menu available.
1231
*/
1232
public function load_custom_menu($config_id = null) {
1233
if ( $config_id === null ) {
@@ -1240,6 +1274,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1240
1241
$this->loaded_menu_config_id = $config_id;
1242
1243
if ( $config_id === 'network-admin' ) {
1244
if ( empty($this->options['custom_network_menu']) ) {
1245
return null;
@@ -1292,6 +1330,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1292
return ($this->options['menu_config_scope'] === 'site');
1293
}
1294
1295
/**
1296
* Determine if the current user may use the menu editor.
1297
*
@@ -1549,6 +1595,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1549
1550
//Lets merge in the unused items.
1551
$max_menu_position = !empty($positions_by_template) ? max($positions_by_template) : 100;
1552
foreach ($this->item_templates as $template_id => $template){
1553
//Skip used menus and separators
1554
if ( !empty($template['used']) || !empty($template['defaults']['separator'])) {
@@ -1561,6 +1608,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1561
$entry['defaults'] = $template['defaults'];
1562
$entry['unused'] = true; //Note that this item is unused
1563
1564
if ($this->options['unused_item_position'] === 'relative') {
1565
1566
//Attempt to maintain relative menu order.
@@ -1677,6 +1726,20 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1677
$this->page_access_lookup[$item['url']][$priority] = $item['access_level'];
1678
}
1679
1680
/**
1681
* Generate WP-compatible $menu and $submenu arrays from a custom menu tree.
1682
*
@@ -1747,6 +1810,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1747
$this->page_access_lookup[$url] = reset($capabilities);
1748
}
1749
1750
//Convert the prepared tree to the internal WordPress format.
1751
foreach($new_tree as $topmenu) {
1752
$trueAccess = isset($this->page_access_lookup[$topmenu['url']]) ? $this->page_access_lookup[$topmenu['url']] : null;
@@ -2398,6 +2465,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2398
}
2399
}
2400
2401
//How verbose "access denied" errors should be.
2402
if ( !empty($this->post['error_verbosity']) ) {
2403
$error_verbosity = intval($this->post['error_verbosity']);
@@ -3466,6 +3541,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
3466
return $log;
3467
}
3468
3469
/**
3470
* WPML support: Update strings that need translation.
3471
*
@@ -4050,6 +4129,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
4050
'className' => 'ameAdminCss',
4051
'title' => 'Admin CSS',
4052
),*/
4053
'hide-admin-menu' => array(
4054
'relativePath' => 'extras/modules/hide-admin-menu/hide-admin-menu.php',
4055
'className' => 'ameAdminMenuHider',
17
require $thisDirectory . '/auto-versioning.php';
18
require $thisDirectory . '/../ajax-wrapper/AjaxWrapper.php';
19
require $thisDirectory . '/module.php';
20
+ require $thisDirectory . '/persistent-module.php';
21
22
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
23
const WPML_CONTEXT = 'admin-menu-editor menu texts';
120
*/
121
private $caps_used_in_menu = array();
122
123
+ public $is_access_test = false;
124
+ private $test_menu = null;
125
+ /**
126
+ * @var ameAccessTestRunner|null
127
+ */
128
+ private $access_test_runner = null;
129
+
130
function init(){
131
$this->sitewide_options = true;
132
176
//the current site but did not exist on the site where the user last edited the menu configuration.
177
'unused_item_position' => 'relative', //"relative" or "bottom".
178
179
+ //Permissions for menu items that are not part of the save menu configuration.
180
+ //The default is to leave the permissions unchanged.
181
+ 'unused_item_permissions' => 'unchanged', //"unchanged" or "match_plugin_access".
182
+
183
//Verbosity level of menu permission errors.
184
'error_verbosity' => self::VERBOSITY_NORMAL,
185
305
//Multisite: Clear role and capability caches when switching to another site.
306
add_action('switch_blog', array($this, 'clear_site_specific_caches'), 10, 0);
307
308
+ //"Test Access" feature.
309
+ if ( (defined('DOING_AJAX') && DOING_AJAX) || isset($this->get['ame-test-menu-access-as']) ) {
310
+ require_once 'access-test-runner.php';
311
+ $this->access_test_runner = new ameAccessTestRunner($this, $this->get);
312
+ }
313
+
314
//Utility actions. Modules can use them in their templates.
315
add_action('admin_menu_editor-display_tabs', array($this, 'display_editor_tabs'));
316
add_action('admin_menu_editor-display_header', array($this, 'display_settings_page_header'));
473
if ( is_network_admin() ) {
474
$screen_hook_name .= '-network';
475
}
476
+ if ( $this->current_tab === 'editor' ) {
477
+ add_meta_box("ws-ame-screen-options", "[AME placeholder]", '__return_false', $screen_hook_name);
478
+ }
479
}
480
481
//Compatibility fix for the WooCommerce order count bubble. Must be run before storing or processing $submenu.
528
$this->build_custom_wp_menu($this->merged_custom_menu['tree']);
529
$this->user_cap_cache_enabled = false;
530
531
+ if ( $this->is_access_test ) {
532
+ $this->access_test_runner['wasCustomMenuApplied'] = true;
533
+ $this->access_test_runner->setCurrentMenuItem($this->get_current_menu_item());
534
+ }
535
+
536
if ( !$this->user_can_access_current_page() ) {
537
$this->log_security_note('DENY access.');
538
+ if ( $this->is_access_test ) {
539
+ $this->access_test_runner['userCanAccessCurrentPage'] = false;
540
+ }
541
+
542
$message = 'You do not have sufficient permissions to access this admin page.';
543
544
if ( ($this->options['error_verbosity'] >= self::VERBOSITY_NORMAL) ) {
561
wp_die($message);
562
} else {
563
$this->log_security_note('ALLOW access.');
564
+ if ( $this->is_access_test ) {
565
+ $this->access_test_runner['userCanAccessCurrentPage'] =
566
+ ($this->access_test_runner['currentMenuItem'] !== null);
567
+ }
568
}
569
570
//Replace the admin menu just before it is displayed and restore it afterwards.
1065
return array(
1066
'user_login' => $user->get('user_login'),
1067
'id' => $user->ID,
1068
+ 'roles' => !empty($user->roles) ? array_values((array)($user->roles)) : array(),
1069
'capabilities' => $this->castValuesToBool($user->caps),
1070
'meta_capabilities' => array(),
1071
'display_name' => $user->display_name,
1261
*
1262
* @param null $config_id
1263
* @return array|null Either a menu in the internal format, or NULL if there is no custom menu available.
1264
+ * @throws InvalidMenuException
1265
*/
1266
public function load_custom_menu($config_id = null) {
1267
if ( $config_id === null ) {
1274
1275
$this->loaded_menu_config_id = $config_id;
1276
1277
+ if ( $this->is_access_test ) {
1278
+ return $this->test_menu;
1279
+ }
1280
+
1281
if ( $config_id === 'network-admin' ) {
1282
if ( empty($this->options['custom_network_menu']) ) {
1283
return null;
1330
return ($this->options['menu_config_scope'] === 'site');
1331
}
1332
1333
+ function save_options() {
1334
+ if ( $this->is_access_test ) {
1335
+ //Don't change live settings during an access test.
1336
+ return false;
1337
+ }
1338
+ return parent::save_options();
1339
+ }
1340
+
1341
/**
1342
* Determine if the current user may use the menu editor.
1343
*
1595
1596
//Lets merge in the unused items.
1597
$max_menu_position = !empty($positions_by_template) ? max($positions_by_template) : 100;
1598
+ $new_grant_access = $this->get_new_menu_grant_access();
1599
foreach ($this->item_templates as $template_id => $template){
1600
//Skip used menus and separators
1601
if ( !empty($template['used']) || !empty($template['defaults']['separator'])) {
1608
$entry['defaults'] = $template['defaults'];
1609
$entry['unused'] = true; //Note that this item is unused
1610
1611
+ $entry['grant_access'] = $new_grant_access;
1612
+
1613
if ($this->options['unused_item_position'] === 'relative') {
1614
1615
//Attempt to maintain relative menu order.
1726
$this->page_access_lookup[$item['url']][$priority] = $item['access_level'];
1727
}
1728
1729
+ /**
1730
+ * Get the access settings for menu items that are not part of the saved menu configuration.
1731
+ *
1732
+ * Typically, this applies to new menus that were added by recently activated plugins.
1733
+ *
1734
+ * @return array
1735
+ */
1736
+ public function get_new_menu_grant_access() {
1737
+ if ( $this->options['unused_item_permissions'] === 'unchanged' ) {
1738
+ return array();
1739
+ }
1740
+ return apply_filters('admin_menu_editor-new_menu_grant_access', array());
1741
+ }
1742
+
1743
/**
1744
* Generate WP-compatible $menu and $submenu arrays from a custom menu tree.
1745
*
1810
$this->page_access_lookup[$url] = reset($capabilities);
1811
}
1812
1813
+ if ( $this->is_access_test ) {
1814
+ $this->access_test_runner->onFinalTreeReady($new_tree);
1815
+ }
1816
+
1817
//Convert the prepared tree to the internal WordPress format.
1818
foreach($new_tree as $topmenu) {
1819
$trueAccess = isset($this->page_access_lookup[$topmenu['url']]) ? $this->page_access_lookup[$topmenu['url']] : null;
2465
}
2466
}
2467
2468
+ //Permissions for unused menu items.
2469
+ if (
2470
+ isset($this->post['unused_item_permissions'])
2471
+ && in_array($this->post['unused_item_permissions'], array('unchanged', 'match_plugin_access'), true)
2472
+ ) {
2473
+ $this->options['unused_item_permissions'] = strval($this->post['unused_item_permissions']);
2474
+ }
2475
+
2476
//How verbose "access denied" errors should be.
2477
if ( !empty($this->post['error_verbosity']) ) {
2478
$error_verbosity = intval($this->post['error_verbosity']);
3541
return $log;
3542
}
3543
3544
+ public function get_security_log() {
3545
+ return $this->security_log;
3546
+ }
3547
+
3548
/**
3549
* WPML support: Update strings that need translation.
3550
*
4129
'className' => 'ameAdminCss',
4130
'title' => 'Admin CSS',
4131
),*/
4132
+ /*'tweaks' => array(
4133
+ 'relativePath' => 'modules/tweaks/tweaks.php',
4134
+ 'className' => 'ameTweakManager',
4135
+ 'title' => 'Tweaks',
4136
+ ),*/
4137
'hide-admin-menu' => array(
4138
'relativePath' => 'extras/modules/hide-admin-menu/hide-admin-menu.php',
4139
'className' => 'ameAdminMenuHider',
includes/module.php CHANGED
@@ -131,4 +131,20 @@ abstract class ameModule {
131
public function handleSettingsForm($post = array()) {
132
//Override this method to process a form submitted from the module's tab.
133
}
134
}
131
public function handleSettingsForm($post = array()) {
132
//Override this method to process a form submitted from the module's tab.
133
}
134
+
135
+ protected function getScopedOption($name, $defaultValue = null) {
136
+ if ( $this->menuEditor->get_plugin_option('menu_config_scope') === 'site' ) {
137
+ return get_option($name, $defaultValue);
138
+ } else {
139
+ return get_site_option($name, $defaultValue);
140
+ }
141
+ }
142
+
143
+ protected function setScopedOption($name, $value, $autoload = null) {
144
+ if ( $this->menuEditor->get_plugin_option('menu_config_scope') === 'site' ) {
145
+ update_option($name, $value, $autoload);
146
+ } else {
147
+ WPMenuEditor::atomic_update_site_option($name, $value);
148
+ }
149
+ }
150
}
includes/persistent-module.php ADDED
@@ -0,0 +1,62 @@
1
+ <?php
2
+
3
+ abstract class amePersistentModule extends ameModule {
4
+ /**
5
+ * @var string Database option where module settings are stored.
6
+ */
7
+ protected $optionName = '';
8
+
9
+ /**
10
+ * @var array|null Module settings. NULL when settings haven't been loaded yet.
11
+ */
12
+ protected $settings = null;
13
+
14
+ /**
15
+ * @var array Default module settings.
16
+ */
17
+ protected $defaultSettings = array();
18
+
19
+ public function __construct($menuEditor) {
20
+ if ( $this->optionName === '' ) {
21
+ throw new LogicException(__CLASS__ . '::$optionName is an empty string. You must set it to a valid option name.');
22
+ }
23
+
24
+ parent::__construct($menuEditor);
25
+ }
26
+
27
+ public function loadSettings() {
28
+ if ( isset($this->settings) ) {
29
+ return $this->settings;
30
+ }
31
+
32
+ $json = $this->getScopedOption($this->optionName, null);
33
+ if ( is_string($json) && !empty($json) ) {
34
+ $settings = json_decode($json, true);
35
+ } else {
36
+ $settings = array();
37
+ }
38
+
39
+ $this->settings = array_merge($this->defaultSettings, $settings);
40
+
41
+ return $this->settings;
42
+ }
43
+
44
+ public function saveSettings() {
45
+ $settings = json_encode($this->settings);
46
+ //Save per site or site-wide based on plugin configuration.
47
+ $this->setScopedOption($this->optionName, $settings);
48
+ }
49
+
50
+ protected function getTemplateVariables($templateName) {
51
+ $variables = parent::getTemplateVariables($templateName);
52
+ if ( $templateName === $this->moduleId ) {
53
+ $variables = array_merge(
54
+ $variables,
55
+ array(
56
+ 'settings' => $this->loadSettings(),
57
+ )
58
+ );
59
+ }
60
+ return $variables;
61
+ }
62
+ }
includes/settings-page.php CHANGED
@@ -117,6 +117,8 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
117
</td>
118
</tr>
119
120
<tr>
121
<th scope="row">
122
Modules
@@ -261,6 +263,51 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
261
</fieldset>
262
</td>
263
</tr>
264
<?php endif; ?>
265
266
<tr>
117
</td>
118
</tr>
119
120
+ <?php do_action('admin-menu-editor-display_addons'); ?>
121
+
122
<tr>
123
<th scope="row">
124
Modules
263
</fieldset>
264
</td>
265
</tr>
266
+
267
+ <tr>
268
+ <th scope="row">
269
+ New menu visibility
270
+ <a class="ws_tooltip_trigger"
271
+ title="This setting controls the default permissions of menu items that are
272
+ not present in the last saved menu configuration.
273
+ &lt;br&gt;&lt;br&gt;
274
+ This includes new menus added by plugins and themes.
275
+ In Multisite, it also applies to menus that exist on some sites but not others.
276
+ It doesn't affect menu items that you add through the Admin Menu Editor interface.">
277
+ <div class="dashicons dashicons-info"></div>
278
+ </a>
279
+ </th>
280
+ <td>
281
+ <fieldset>
282
+ <p>
283
+ <label>
284
+ <input type="radio" name="unused_item_permissions" value="unchanged"
285
+ <?php checked('unchanged', $settings['unused_item_permissions']); ?>>
286
+ Leave unchanged (default)
287
+
288
+ <br><span class="description">
289
+ No special restrictions. Visibility will depend on the plugin
290
+ that added the menus.
291
+ </span>
292
+ </label>
293
+ </p>
294
+
295
+ <p>
296
+ <label>
297
+ <input type="radio" name="unused_item_permissions" value="match_plugin_access"
298
+ <?php checked('match_plugin_access', $settings['unused_item_permissions']); ?>>
299
+ Show only to users who can access this plugin
300
+
301
+ <br><span class="description">
302
+ Automatically hides all new and unrecognized menus from regular users.
303
+ To make new menus visible, you have to manually enable them in the menu editor.
304
+ </span>
305
+ </label>
306
+ </p>
307
+
308
+ </fieldset>
309
+ </td>
310
+ </tr>
311
<?php endif; ?>
312
313
<tr>
includes/shadow_plugin_framework.php CHANGED
@@ -58,8 +58,8 @@ class MenuEd_ShadowPluginFramework {
58
/************************************
59
Add the default hooks
60
************************************/
61
- add_action('activate_'.$this->plugin_basename, array(&$this,'activate'));
62
- add_action('deactivate_'.$this->plugin_basename, array(&$this,'deactivate'));
63
64
$this->init(); //Call the plugin's init() function
65
$this->init_finish(); //Complete initialization by loading settings, etc
@@ -97,7 +97,7 @@ class MenuEd_ShadowPluginFramework {
97
98
//Add a "Settings" action link
99
if ($this->settings_link)
100
- add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
101
102
if ($this->magic_hooks)
103
$this->set_magic_hooks();
@@ -240,7 +240,7 @@ class MenuEd_ShadowPluginFramework {
240
//Get the hook's tag from the method name
241
$hook = substr($method->name, 5);
242
//Add the hook. Uses add_filter because add_action is simply a wrapper of the same.
243
- add_filter($hook, array(&$this, $method->name),
244
$this->get_magic_hook_priority(), $method->getNumberOfParameters());
245
}
246
}
@@ -282,8 +282,9 @@ class MenuEd_ShadowPluginFramework {
282
* @return array
283
*/
284
function plugin_action_links($links, $file) {
285
- if ($file == $this->plugin_basename)
286
- $links[] = "<a href='" . $this->settings_link . "'>" . __('Settings') . "</a>";
287
return $links;
288
}
289
58
/************************************
59
Add the default hooks
60
************************************/
61
+ add_action('activate_'.$this->plugin_basename, array($this,'activate'));
62
+ add_action('deactivate_'.$this->plugin_basename, array($this,'deactivate'));
63
64
$this->init(); //Call the plugin's init() function
65
$this->init_finish(); //Complete initialization by loading settings, etc
97
98
//Add a "Settings" action link
99
if ($this->settings_link)
100
+ add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
101
102
if ($this->magic_hooks)
103
$this->set_magic_hooks();
240
//Get the hook's tag from the method name
241
$hook = substr($method->name, 5);
242
//Add the hook. Uses add_filter because add_action is simply a wrapper of the same.
243
+ add_filter($hook, array($this, $method->name),
244
$this->get_magic_hook_priority(), $method->getNumberOfParameters());
245
}
246
}
282
* @return array
283
*/
284
function plugin_action_links($links, $file) {
285
+ if (($file == $this->plugin_basename) && is_array($links)) {
286
+ $links[] = "<a href='" . $this->settings_link . "'>" . __('Settings') . "</a>";
287
+ }
288
return $links;
289
}
290
includes/test-access-screen.php ADDED
@@ -0,0 +1,67 @@
1
+ <div id="ws_ame_test_access_screen">
2
+ <div id="ws_ame_test_inputs">
3
+ <label class="ws_ame_test_input">
4
+ <span class="ws_ame_test_input_name">Menu item</span>
5
+ <select name="ws_ame_test_menu_item" id="ws_ame_test_menu_item" class="ws_ame_test_input_value"></select>
6
+ </label>
7
+
8
+ <label class="ws_ame_test_input">
9
+ <span class="ws_ame_test_input_name">Log in as user</span>
10
+ <input type="text" class="ws_ame_test_input_value" id="ws_ame_test_access_username">
11
+ </label>
12
+
13
+ <label class="ws_ame_test_input">
14
+ <span class="ws_ame_test_input_name">Relevant role (optional)</span>
15
+ <select name="ws_ame_test_relevant_actor" id="ws_ame_test_relevant_actor" class="ws_ame_test_input_value"></select>
16
+ </label>
17
+
18
+ <div class="ws_ame_test_input">
19
+ <span class="ws_ame_test_input_name">What you want to happen</span>
20
+ <fieldset class="ws_ame_test_input_value">
21
+ <label>
22
+ <input type="radio" name="ws_ame_desired_test_outcome" value="visible" checked>
23
+ Menu is <strong>visible</strong>
24
+ </label><br>
25
+ <label>
26
+ <input type="radio" name="ws_ame_desired_test_outcome" value="hidden">
27
+ Menu is <strong>hidden</strong>
28
+ </label><br>
29
+ </fieldset>
30
+ </div>
31
+
32
+ <div id="ws_ame_test_actions">
33
+ <div id="ws_ame_test_button_container">
34
+ <input type="button" class="button-primary" value="Start Test" id="ws_ame_start_access_test">
35
+ </div>
36
+ <div id="ws_ame_test_progress">
37
+ <span class="spinner is-active"></span>
38
+ <span id="ws_ame_test_progress_text">
39
+ Test hasn't started yet.
40
+ </span>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="clear"></div>
45
+ </div>
46
+
47
+ <div id="ws_ame_test_access_body">
48
+ <div id="ws_ame_test_frame_container">
49
+ <span id="ws_ame_test_frame_placeholder">
50
+ <em>Test page will appear here.</em><br>
51
+ </span>
52
+ <iframe src="" frameborder="0" sandbox="allow-scripts" id="ws_ame_test_access_frame"></iframe>
53
+ </div>
54
+
55
+ <div id="ws_ame_test_access_sidebar">
56
+ <span id="ws_ame_test_output_placeholder">
57
+ <em>Analysis will appear here.</em>
58
+ </span>
59
+ <div id="ws_ame_test_output">
60
+ <h4>Result</h4>
61
+
62
+ <h4>Analysis</h4>
63
+ <h4>Suggestions</h4>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
js/menu-editor.js CHANGED
@@ -46,6 +46,9 @@
46
* @property {string|null} wsEditorData.selectedMenu
47
* @property {string|null} wsEditorData.selectedSubmenu
48
*
49
* @property {boolean} wsEditorData.isDemoMode
50
* @property {boolean} wsEditorData.isMasterMode
51
*/
@@ -774,6 +777,15 @@ var knownMenuFields = {
774
}
775
}),
776
777
'icon_url' : $.extend({}, baseField, {
778
caption: 'Icon URL',
779
type : 'icon_selector',
@@ -834,10 +846,50 @@ var knownMenuFields = {
834
}
835
}),
836
837
- 'css_class' : $.extend({}, baseField, {
838
- caption: 'CSS classes',
839
advanced : true,
840
- onlyForTopMenus: true
841
}),
842
843
'open_in' : $.extend({}, baseField, {
@@ -888,48 +940,24 @@ var knownMenuFields = {
888
}
889
}),
890
891
- 'colors' : $.extend({}, baseField, {
892
- caption: 'Color scheme',
893
- defaultValue: 'Default',
894
- type: 'color_scheme_editor',
895
- onlyForTopMenus: true,
896
- visible: false,
897
advanced : true,
898
899
- display: function(menuItem, displayValue, input, containerNode) {
900
- var colors = getFieldValue(menuItem, 'colors', {}) || {};
901
- var colorList = containerNode.find('.ws_color_scheme_display');
902
-
903
- colorList.empty();
904
- var count = 0, maxColorsToShow = 7;
905
-
906
- $.each(colors, function(name, value) {
907
- if ( !value || (count >= maxColorsToShow) ) {
908
- return;
909
- }
910
-
911
- colorList.append(
912
- $('<span></span>').addClass('ws_color_display_item').css('background-color', value)
913
- );
914
- count++;
915
- });
916
-
917
- if (count === 0) {
918
- colorList.append('Default');
919
- }
920
-
921
- return 'Placeholder. You should never see this.';
922
- },
923
-
924
- write: function(menuItem) {
925
- //Menu colors can't be directly edited.
926
- }
927
}),
928
929
- 'page_title' : $.extend({}, baseField, {
930
- caption: "Window title",
931
- standardCaption : true,
932
- advanced : true
933
}),
934
935
'page_heading' : $.extend({}, baseField, {
@@ -939,14 +967,14 @@ var knownMenuFields = {
939
visible: false
940
}),
941
942
- 'hookname' : $.extend({}, baseField, {
943
- caption: 'Hook name',
944
- advanced : true,
945
- onlyForTopMenus: true
946
}),
947
948
'is_always_open' : $.extend({}, baseField, {
949
- caption: 'Keep this menu open',
950
advanced : true,
951
onlyForTopMenus: true,
952
type: 'checkbox',
@@ -1054,6 +1082,10 @@ function buildEditboxField(entry, field_name, field_settings){
1054
.add('<input type="button" class="button ws_open_color_editor" value="Edit...">');
1055
break;
1056
1057
case 'text':
1058
/* falls through */
1059
default:
@@ -1068,6 +1100,9 @@ function buildEditboxField(entry, field_name, field_settings){
1068
if (!field_settings.standardCaption) {
1069
className += ' ws_no_field_caption';
1070
}
1071
1072
var caption = '';
1073
if (field_settings.standardCaption) {
@@ -1094,7 +1129,7 @@ function buildEditboxField(entry, field_name, field_settings){
1094
1095
editField
1096
.append(
1097
- $('<img class="ws_reset_button" title="Reset to default value">')
1098
.attr('src', wsEditorData.imagesUrl + '/transparent16.png')
1099
).data('field_name', field_name);
1100
@@ -1725,6 +1760,10 @@ function readAllFields(container){
1725
if (field_name === 'embedded_page_id') {
1726
return true;
1727
}
1728
1729
//Find the field (usually an input or select element).
1730
var input_box = field.find('.ws_field_value');
@@ -1997,6 +2036,8 @@ function ameOnDomReady() {
1997
knownMenuFields.access_level.visible = true;
1998
knownMenuFields.page_heading.visible = true;
1999
knownMenuFields.colors.visible = true;
2000
knownMenuFields.extra_capability.visible = false; //Superseded by the "access_level" field.
2001
2002
//The Pro version supports submenu icons, but they can be disabled by the user.
@@ -4787,6 +4828,180 @@ function ameOnDomReady() {
4787
}
4788
}
4789
4790
//Finally, show the menu
4791
loadMenuConfiguration(customMenu);
4792
46
* @property {string|null} wsEditorData.selectedMenu
47
* @property {string|null} wsEditorData.selectedSubmenu
48
*
49
+ * @property {string} wsEditorData.setTestConfigurationNonce
50
+ * @property {string} wsEditorData.testAccessNonce
51
+ *
52
* @property {boolean} wsEditorData.isDemoMode
53
* @property {boolean} wsEditorData.isMasterMode
54
*/
777
}
778
}),
779
780
+ 'appearance_heading' : $.extend({}, baseField, {
781
+ caption: 'Appearance',
782
+ advanced : true,
783
+ onlyForTopMenus: false,
784
+ type: 'heading',
785
+ standardCaption: false,
786
+ visible: false //Only visible in the Pro version.
787
+ }),
788
+
789
'icon_url' : $.extend({}, baseField, {
790
caption: 'Icon URL',
791
type : 'icon_selector',
846
}
847
}),
848
849
+ 'colors' : $.extend({}, baseField, {
850
+ caption: 'Color scheme',
851
+ defaultValue: 'Default',
852
+ type: 'color_scheme_editor',
853
+ onlyForTopMenus: true,
854
+ visible: false,
855
advanced : true,
856
+
857
+ display: function(menuItem, displayValue, input, containerNode) {
858
+ var colors = getFieldValue(menuItem, 'colors', {}) || {};
859
+ var colorList = containerNode.find('.ws_color_scheme_display');
860
+
861
+ colorList.empty();
862
+ var count = 0, maxColorsToShow = 7;
863
+
864
+ $.each(colors, function(name, value) {
865
+ if ( !value || (count >= maxColorsToShow) ) {
866
+ return;
867
+ }
868
+
869
+ colorList.append(
870
+ $('<span></span>').addClass('ws_color_display_item').css('background-color', value)
871
+ );
872
+ count++;
873
+ });
874
+
875
+ if (count === 0) {
876
+ colorList.append('Default');
877
+ }
878
+
879
+ return 'Placeholder. You should never see this.';
880
+ },
881
+
882
+ write: function(menuItem) {
883
+ //Menu colors can't be directly edited.
884
+ }
885
+ }),
886
+
887
+ 'html_heading' : $.extend({}, baseField, {
888
+ caption: 'HTML',
889
+ advanced : true,
890
+ onlyForTopMenus: true,
891
+ type: 'heading',
892
+ standardCaption: false
893
}),
894
895
'open_in' : $.extend({}, baseField, {
940
}
941
}),
942
943
+ 'css_class' : $.extend({}, baseField, {
944
+ caption: 'CSS classes',
945
advanced : true,
946
+ onlyForTopMenus: true
947
+ }),
948
949
+ 'hookname' : $.extend({}, baseField, {
950
+ caption: 'ID attribute',
951
+ advanced : true,
952
+ onlyForTopMenus: true
953
}),
954
955
+ 'page_properties_heading' : $.extend({}, baseField, {
956
+ caption: 'Page',
957
+ advanced : true,
958
+ onlyForTopMenus: true,
959
+ type: 'heading',
960
+ standardCaption: false
961
}),
962
963
'page_heading' : $.extend({}, baseField, {
967
visible: false
968
}),
969
970
+ 'page_title' : $.extend({}, baseField, {
971
+ caption: "Window title",
972
+ standardCaption : true,
973
+ advanced : true
974
}),
975
976
'is_always_open' : $.extend({}, baseField, {
977
+ caption: 'Keep this menu expanded',
978
advanced : true,
979
onlyForTopMenus: true,
980
type: 'checkbox',
1082
.add('<input type="button" class="button ws_open_color_editor" value="Edit...">');
1083
break;
1084
1085
+ case 'heading':
1086
+ inputBox = $('<span>' + field_settings.caption + '</span>');
1087
+ break;
1088
+
1089
case 'text':
1090
/* falls through */
1091
default:
1100
if (!field_settings.standardCaption) {
1101
className += ' ws_no_field_caption';
1102
}
1103
+ if (field_settings.type === 'heading') {
1104
+ className += ' ws_field_group_heading';
1105
+ }
1106
1107
var caption = '';
1108
if (field_settings.standardCaption) {
1129
1130
editField
1131
.append(
1132
+ $('<img class="ws_reset_button" title="Reset to default value" src="">')
1133
.attr('src', wsEditorData.imagesUrl + '/transparent16.png')
1134
).data('field_name', field_name);
1135
1760
if (field_name === 'embedded_page_id') {
1761
return true;
1762
}
1763
+ //Headings contain no useful data.
1764
+ if (field.hasClass('ws_field_group_heading')) {
1765
+ return true;
1766
+ }
1767
1768
//Find the field (usually an input or select element).
1769
var input_box = field.find('.ws_field_value');
2036
knownMenuFields.access_level.visible = true;
2037
knownMenuFields.page_heading.visible = true;
2038
knownMenuFields.colors.visible = true;
2039
+ knownMenuFields.appearance_heading.visible = true;
2040
+ knownMenuFields.appearance_heading.onlyForTopMenus = false;
2041
knownMenuFields.extra_capability.visible = false; //Superseded by the "access_level" field.
2042
2043
//The Pro version supports submenu icons, but they can be disabled by the user.
4828
}
4829
}
4830
4831
+ /******************************************************************
4832
+ "Test Access" feature
4833
+ ******************************************************************/
4834
+ var testAccessDialog = $('#ws_ame_test_access_screen').dialog({
4835
+ autoOpen: false,
4836
+ modal: true,
4837
+ closeText: ' ',
4838
+ title: 'Test access',
4839
+ width: 900
4840
+ //draggable: false
4841
+ }),
4842
+ testMenuItemList = $('#ws_ame_test_menu_item'),
4843
+ testActorList = $('#ws_ame_test_relevant_actor'),
4844
+ testAccessButton = $('#ws_ame_start_access_test'),
4845
+ testAccessFrame = $('#ws_ame_test_access_frame'),
4846
+ testConfig = null,
4847
+
4848
+ testProgress = $('#ws_ame_test_progress'),
4849
+ testProgressText = $('#ws_ame_test_progress_text');
4850
+
4851
+ $('#ws_test_access').click(function () {
4852
+ testConfig = readMenuTreeState();
4853
+
4854
+ var selectedMenuContainer = getSelectedMenu(),
4855
+ selectedItemContainer = getSelectedSubmenuItem(),
4856
+ selectedMenu = null,
4857
+ selectedItem = null,
4858
+ selectedUrl = null;
4859
+ if (selectedMenuContainer.length > 0) {
4860
+ selectedMenu = selectedMenuContainer.data('menu_item');
4861
+ selectedUrl = getFieldValue(selectedMenu, 'url');
4862
+ }
4863
+ if (selectedItemContainer.length > 0) {
4864
+ selectedItem = selectedItemContainer.data('menu_item');
4865
+ selectedUrl = getFieldValue(selectedItem, 'url');
4866
+ }
4867
+
4868
+ function addMenuItems(collection, parentTitle, parentFile) {
4869
+ _.each(collection, function (menuItem) {
4870
+ if (menuItem.separator) {
4871
+ return;
4872
+ }
4873
+
4874
+ var title = formatMenuTitle(getFieldValue(menuItem, 'menu_title', '[Untitled menu]'));
4875
+ if (parentTitle) {
4876
+ title = parentTitle + ' -> ' + title;
4877
+ }
4878
+ var url = getFieldValue(menuItem, 'url', '[no-url]');
4879
+
4880
+ var option = $(
4881
+ '<option>', {
4882
+ val: url,
4883
+ text: title
4884
+ }
4885
+ );
4886
+ option.data('menu_item', menuItem);
4887
+ option.data('parent_file', parentFile || '');
4888
+ option.prop('selected', (url === selectedUrl));
4889
+
4890
+ testMenuItemList.append(option);
4891
+
4892
+ if (menuItem.items) {
4893
+ addMenuItems(menuItem.items, title, getFieldValue(menuItem, 'file', ''));
4894
+ }
4895
+ });
4896
+ }
4897
+
4898
+ //Populate the list of menu items.
4899
+ testMenuItemList.empty();
4900
+ addMenuItems(testConfig.tree);
4901
+
4902
+ //Populate the actor list.
4903
+ testActorList.empty();
4904
+ testActorList.append($('<option>', {text: 'Not selected', val: ''}));
4905
+ _.each(actorSelectorWidget.getVisibleActors(), function (actor) {
4906
+ //TODO: Skip anything that isn't a role
4907
+ var option = $('<option>', {
4908
+ val: actor.id,
4909
+ text: actorSelectorWidget.getNiceName(actor)
4910
+ });
4911
+ testActorList.append(option);
4912
+ });
4913
+
4914
+ //Pre-select the current actor.
4915
+ if (actorSelectorWidget.selectedActor !== null) {
4916
+ testActorList.val(actorSelectorWidget.selectedActor);
4917
+ }
4918
+
4919
+ testAccessDialog.dialog('open');
4920
+ });
4921
+
4922
+ testAccessButton.click(function () {
4923
+ testAccessButton.prop('disabled', true);
4924
+ testProgress.show();
4925
+ testProgressText.text('Sending menu settings...');
4926
+
4927
+ var selectedOption = testMenuItemList.find('option:selected').first(),
4928
+ selectedMenu = selectedOption.data('menu_item'),
4929
+ menuUrl = selectedOption.val();
4930
+
4931
+ $.ajax(
4932
+ wsEditorData.adminAjaxUrl,
4933
+ {
4934
+ data: {
4935
+ 'action': 'ws_ame_set_test_configuration',
4936
+ 'data': encodeMenuAsJSON(testConfig),
4937
+ '_ajax_nonce': wsEditorData.setTestConfigurationNonce
4938
+ },
4939
+ method: 'post',
4940
+ dataType: 'json',
4941
+ success: function(response, textStatus) {
4942
+ if (!response) {
4943
+ alert('Error: Could not parse the server response.');
4944
+ testAccessButton.prop('disabled', false);
4945
+ return;
4946
+ }
4947
+ if (response.error) {
4948
+ alert(response.error);
4949
+ testAccessButton.prop('disabled', false);
4950
+ return;
4951
+ }
4952
+ if (!response.success) {
4953
+ alert('Error: The request failed, but there is no error information available.');
4954
+ testAccessButton.prop('disabled', false);
4955
+ return;
4956
+ }
4957
+
4958
+ //Caution: Won't work in IE. Needs compat checks.
4959
+ var testPageUrl = new URL(menuUrl, window.location.href);
4960
+ testPageUrl.searchParams.append('ame-test-menu-access-as', $('#ws_ame_test_access_username').val());
4961
+ testPageUrl.searchParams.append('_wpnonce', wsEditorData.testAccessNonce);
4962
+ testPageUrl.searchParams.append('ame-test-relevant-role', testActorList.val());
4963
+
4964
+ testPageUrl.searchParams.append('ame-test-target-item', getFieldValue(selectedMenu, 'file', ''));
4965
+ testPageUrl.searchParams.append('ame-test-target-parent', selectedOption.data('parent_file'));
4966
+
4967
+ testProgressText.text('Loading the test page....');
4968
+ $('#ws_ame_test_frame_placeholder').hide();
4969
+
4970
+ $(window).on('message', receiveTestAccessResults);
4971
+ testAccessFrame
4972
+ .show()
4973
+ .on('load', onAccessTestLoaded)
4974
+ .prop('src', testPageUrl.href);
4975
+ },
4976
+ error: function(jqXHR, textStatus) {
4977
+ alert('HTTP Error: ' + textStatus);
4978
+ testAccessButton.prop('disabled', false);
4979
+ }
4980
+ }
4981
+ );
4982
+ });
4983
+
4984
+ function onAccessTestLoaded() {
4985
+ testAccessFrame.off('load', onAccessTestLoaded);
4986
+ testProgress.hide();
4987
+
4988
+ testAccessButton.prop('disabled', false);
4989
+ }
4990
+
4991
+ function receiveTestAccessResults(event) {
4992
+ if (event.originalEvent.source !== testAccessFrame.get(0).contentWindow) {
4993
+ if (console && console.warn) {
4994
+ console.warn('AME: Received a message from an unexpected source. Message ignored.');
4995
+ }
4996
+ return;
4997
+ }
4998
+ var message = event.originalEvent.data || event.originalEvent.message;
4999
+ console.log('message received', message);
5000
+
5001
+ $(window).off('message', receiveTestAccessResults);
5002
+ }
5003
+
5004
+
5005
//Finally, show the menu
5006
loadMenuConfiguration(customMenu);
5007
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.8.2
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.3
7
Author: Janis Elsts
8
Author URI: http://w-shadow.com/blog/
9
*/
modules/tweaks/default-tweaks.php ADDED
@@ -0,0 +1,21 @@
1
+ <?php
2
+ return array(
3
+ 'hide-screen-meta-links' => array(
4
+ 'label' => 'Hide screen meta links',
5
+ 'selector' => '#screen-meta-links'
6
+ ),
7
+ 'hide-screen-options' => array(
8
+ 'label' => 'Hide the "Screen Options" button',
9
+ 'selector' => '#screen-options-link-wrap',
10
+ 'parent' => 'hide-screen-meta-links',
11
+ ),
12
+ 'hide-help-panel' => array(
13
+ 'label' => 'Hide the "Help" button',
14
+ 'selector' => '#contextual-help-link-wrap',
15
+ 'parent' => 'hide-screen-meta-links',
16
+ ),
17
+ 'hide-all-admin-notices' => array(
18
+ 'label' => 'Hide ALL admin notices',
19
+ 'selector' => '.wrap .notice, .wrap .updated',
20
+ ),
21
+ );
modules/tweaks/tweaks-template.php ADDED
@@ -0,0 +1,13 @@
1
+ <?php
2
+ /**
3
+ * @var string $tabUrl Fully qualified URL of the tab.
4
+ * @var array $tweaks
5
+ */
6
+
7
+ ?>
8
+ <div id="ame-tweak-manager">
9
+ <?php require AME_ROOT_DIR . '/modules/actor-selector/actor-selector-template.php'; ?>
10
+
11
+ <pre><?php print_r($tweaks); ?></pre>
12
+ </div>
13
+
modules/tweaks/tweaks.php ADDED
@@ -0,0 +1,158 @@
<
1
+ <?php
2
+
3
+ /*
4
+ * Idea: Show tweaks as options in menu properties, e.g. in a "Tweaks" section styled like the collapsible
5
+ * property sheets in Delphi.
6
+ */
7
+
8
+ class ameTweakManager extends amePersistentModule {
9
+ protected $tabSlug = 'tweaks';
10
+ protected $tabTitle = 'Tweaks';
11
+ protected $optionName = 'ws_ame_tweak_settings';
12
+
13
+ private $tweaks = array();
14
+
15
+ private $postponedTweaks = array();
16
+ private $pendingSelectorTweaks = array();
17
+
18
+ public function __construct($menuEditor) {
19
+ parent::__construct($menuEditor);
20
+
21
+ add_action('init', array($this, 'processTweaks'), 200);
22
+ add_action('admin_head', array($this, 'outputSelectors'));
23
+ }
24
+
25
+ private function registerTweaks() {
26
+ $this->tweaks = require (__DIR__ . '/default-tweaks.php');
27
+ do_action('admin-menu-editor-register_tweaks', $this);
28
+ }
29
+
30
+ public function processTweaks() {
31
+ $settings = $this->loadSettings();
32
+ $isTweakEnabled = ameUtils::get($settings, 'isTweakEnabled');
33
+
34
+ $this->registerTweaks();
35
+
36
+ $currentUser = wp_get_current_user();
37
+ $roles = $this->menuEditor->get_user_roles($currentUser);
38
+ $isSuperAdmin = is_multisite() && is_super_admin($currentUser->ID);
39
+
40
+ foreach ($this->tweaks as $id => $tweak) {
41
+ if ( empty($isTweakEnabled[$id]) ) {
42
+ continue; //This tweak is not enabled for anyone.
43
+ }
44
+
45
+ if ( !$this->appliesToUser($isTweakEnabled[$id], $currentUser, $roles, $isSuperAdmin) ) {
46
+ continue;
47
+ }
48
+
49
+ if ( isset($tweak['initFilter']) && !call_user_func($tweak['initFilter']) ) {
50
+ continue;
51
+ }
52
+
53
+ if ( !empty($tweak['screens']) || !empty($tweak['screenFilter']) ) {
54
+ $this->postponedTweaks[$id] = $tweak;
55
+ continue;
56
+ }
57
+
58
+ $this->applyTweak($id, $tweak);
59
+ }
60
+
61
+ if ( !empty($this->postponedTweaks) ) {
62
+ add_action('current_screen', array($this, 'processPostponedTweaks'), 10, 1);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * @param array $enabledForActor
68
+ * @param WP_User $user
69
+ * @param array $roles
70
+ * @param bool $isSuperAdmin
71
+ * @return bool
72
+ */
73
+ private function appliesToUser($enabledForActor, $user, $roles, $isSuperAdmin = false) {
74
+ //User-specific settings have priority over everything else.
75
+ $userActor = 'user:' . $user->user_login;
76
+ if ( isset($enabledForActor[$userActor]) ) {
77
+ return $enabledForActor[$userActor];
78
+ }
79
+
80
+ //The "Super Admin" flag has priority over regular roles.
81
+ if ( $isSuperAdmin && isset($enabledForActor['special:super_admin']) ) {
82
+ return $enabledForActor['special:super_admin'];
83
+ }
84
+
85
+ //If it's enabled for any role, it's enabled for the user.
86
+ foreach($roles as $role) {
87
+ if ( !empty($enabledForActor['role:' . $role]) ) {
88
+ return true;
89
+ }
90
+ }
91
+
92
+ //By default, all tweaks are disabled.
93
+ return false;
94
+ }
95
+
96
+ private function applyTweak($id, $tweak) {
97
+ //Run callbacks immediately.
98
+ if ( isset($tweak['callback']) ) {
99
+ call_user_func($tweak['callback']);
100
+ }
101
+
102
+ //Queue selectors for later.
103
+ if ( !empty($tweak['selector']) ) {
104
+ $this->pendingSelectorTweaks[$id] = $tweak;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * @param WP_Screen $screen
110
+ */
111
+ public function processPostponedTweaks($screen = null) {
112
+ if ( empty($screen) && function_exists('get_current_screen') ) {
113
+ $screen = get_current_screen();
114
+ }
115
+ $screenId = isset($screen, $screen->id) ? $screen->id : null;
116
+
117
+ foreach($this->postponedTweaks as $id => $tweak) {
118
+ if ( !empty($tweak['screens']) && !in_array($screenId, $tweak['screens']) ) {
119
+ continue;
120
+ }
121
+
122
+ if ( !empty($tweak['screenFilter']) && !call_user_func($tweak['screenFilter'], $screen) ) {
123
+ continue;
124
+ }
125
+
126
+ $this->applyTweak($id, $tweak);
127
+ }
128
+
129
+ $this->postponedTweaks = array();
130
+ }
131
+
132
+ public function outputSelectors() {
133
+ if ( empty($this->pendingSelectorTweaks) ) {
134
+ return;
135
+ }
136
+
137
+ $selectors = array();
138
+ foreach($this->pendingSelectorTweaks as $tweak) {
139
+ $selectors[] = $tweak['selector'];
140
+ }
141
+ $css = sprintf(
142
+ '<style type="text/css">%s { display: none; }</style>',
143
+ implode(',', $selectors)
144
+ );
145
+
146
+ echo '<!-- AME selector tweaks -->', "\n", $css, "\n";
147
+
148
+ $this->pendingSelectorTweaks = array();
149
+ }
150
+
151
+ protected function getTemplateVariables($templateName) {
152
+ $variables = parent::getTemplateVariables($templateName);
153
+ $variables['tweaks'] = $this->tweaks;
154
+ return $variables;