PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | pages/page-mainwp-rest-api-page.php +1731 -431 5.2 → 6.2.2 View file →
@@ -9,8 +9,13 @@
9 9 */
10 10
11 11 namespace MainWP\Dashboard;
12 12
13 +// Exit if accessed directly.
14 +if ( ! defined( 'ABSPATH' ) ) {
15 + exit;
16 +}
17 +
13 18 /**
14 19 * Class MainWP_Rest_Api_Page
15 20 *
16 21 * @package MainWP\Dashboard
@@ -31,8 +36,15 @@
31 36 */
32 37 public static $subPages;
33 38
34 39 /**
40 + * Private variable to hold the application passwords instance.
41 + *
42 + * @var MainWP_Application_Passwords
43 + */
44 + protected static $application_passwords;
45 +
46 + /**
35 47 * Get Class Name
36 48 *
37 49 * @return __CLASS__
38 50 */
@@ -51,8 +63,17 @@
51 63 }
52 64 return static::$instance;
53 65 }
54 66
67 + /**
68 + * Constructor
69 + *
70 + * Sets up the application passwords instance.
71 + */
72 + public function __construct() {
73 + static::$application_passwords = MainWP_Application_Passwords::instance();
74 + }
75 +
55 76 /** Instantiate Hooks for the REST API Page. */
56 77 public static function init() {
57 78 /**
58 79 * Renders the REST API page header via the 'mainwp_pageheader_restapi' action.
@@ -75,8 +96,39 @@
75 96
76 97 /** Run the export_sites method that exports the Child Sites .csv file */
77 98 public function admin_init() {
78 99 MainWP_Post_Handler::instance()->add_action( 'mainwp_rest_api_remove_keys', array( $this, 'ajax_rest_api_remove_keys' ) );
100 +
101 + // Page: Application Passwords.
102 + MainWP_Post_Handler::instance()->add_action( 'mainwp_application_password_create', array( &$this, 'ajax_application_password_create' ) );
103 + MainWP_Post_Handler::instance()->add_action( 'mainwp_application_password_delete', array( &$this, 'ajax_application_password_delete' ) );
104 + MainWP_Post_Handler::instance()->add_action( 'mainwp_application_password_delete_multiple', array( &$this, 'ajax_application_password_delete_multiple' ) );
105 + MainWP_Post_Handler::instance()->add_action( 'mainwp_application_password_delete_all', array( &$this, 'ajax_application_password_delete_all' ) );
106 + // Add update handler for editing application password name.
107 + MainWP_Post_Handler::instance()->add_action( 'mainwp_application_password_update', array( &$this, 'ajax_application_password_update' ) );
108 +
109 + // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
110 + // Redirect to Application Passwords page if user tries to access REST API page and doesn't have permission.
111 + if ( isset( $_GET['page'] ) && 'RESTAPI' === $_GET['page'] && ! static::can_access_rest_api() ) {
112 + // If cannot view REST API, try redirecting to Application Passwords if allowed, otherwise to dashboard.
113 + $redirect = static::$application_passwords->can_access_application_passwords() ? 'admin.php?page=ApplicationPasswords' : 'admin.php?page=mainwp_tab'; // NOSONAR.
114 + wp_safe_redirect( admin_url( $redirect ) ); // NOSONAR.
115 + exit;
116 + }
117 +
118 + // Redirect to Application Passwords page if user tries to access Add API Keys page and doesn't have permission.
119 + // Allow access to Add/Edit API Keys if user has manage/create/edit REST API keys.
120 + if (
121 + isset( $_GET['page'] ) &&
122 + 'AddApiKeys' === $_GET['page'] &&
123 + ! ( static::can_create_rest_api_keys() || static::can_edit_rest_api_keys() )
124 + ) {
125 + $redirect = static::$application_passwords->can_access_application_passwords() ? 'admin.php?page=ApplicationPasswords' : 'admin.php?page=mainwp_tab';
126 + wp_safe_redirect( admin_url( $redirect ) );
127 + exit;
128 + }
129 + // phpcs:enable
130 +
79 131 $this->handle_rest_api_add_new();
80 132 $this->handle_rest_api_edit();
81 133 }
82 134
@@ -86,12 +138,16 @@
86 138 *
87 139 * @uses \MainWP\Dashboard\MainWP_Menu::is_disable_menu_item()
88 140 */
89 141 public static function init_menu() {
142 + $add_api_keys_title = esc_html__( 'Add API Keys', 'mainwp' ); // NOSONAR.
143 + $api_access_title = esc_html__( 'API Access', 'mainwp' ); // NOSONAR.
144 + $application_passwords_title = esc_html__( 'Application Passwords', 'mainwp' ); // NOSONAR.
145 +
90 146 add_submenu_page(
91 147 'mainwp_tab',
92 - esc_html__( 'REST API', 'mainwp' ),
93 - ' <span id="mainwp-rest-api">' . esc_html__( 'REST API', 'mainwp' ) . '</span>',
148 + $api_access_title,
149 + ' <span id="mainwp-rest-api">' . $api_access_title . '</span>',
94 150 'read',
95 151 'RESTAPI',
96 152 array(
97 153 static::get_class_name(),
@@ -101,10 +157,10 @@
101 157
102 158 if ( ! MainWP_Menu::is_disable_menu_item( 3, 'AddApiKeys' ) ) {
103 159 add_submenu_page(
104 160 'mainwp_tab',
105 - esc_html__( 'Add API Keys', 'mainwp' ),
106 - ' <div class="mainwp-hidden">' . esc_html__( 'Add API Keys', 'mainwp' ) . '</div>',
161 + $add_api_keys_title,
162 + ' <div class="mainwp-hidden">' . $add_api_keys_title . '</div>',
107 163 'read',
108 164 'AddApiKeys',
109 165 array(
110 166 static::get_class_name(),
@@ -111,8 +167,21 @@
111 167 'render_rest_api_setings',
112 168 )
113 169 );
114 170 }
171 + if ( ! MainWP_Menu::is_disable_menu_item( 3, 'ApplicationPasswords' ) ) {
172 + add_submenu_page(
173 + 'mainwp_tab',
174 + $application_passwords_title,
175 + ' <div class="mainwp-hidden">' . $application_passwords_title . '</div>',
176 + 'read',
177 + 'ApplicationPasswords',
178 + array(
179 + static::get_class_name(),
180 + 'render_application_passwords',
181 + )
182 + );
183 + }
115 184
116 185 /**
117 186 * REST API Subpages
118 187 *
@@ -141,12 +210,12 @@
141 210 */
142 211 public static function init_subpages_menu() {
143 212 ?>
144 213 <div id="menu-mainwp-RESTAPI" class="mainwp-submenu-wrapper">
145 - <div class="wp-submenu sub-open" style="">
214 + <div class="wp-submenu sub-open">
146 215 <div class="mainwp_boxout">
147 216 <div class="mainwp_boxoutin"></div>
148 - <a href="<?php echo esc_url( admin_url( 'admin.php?page=RESTAPI' ) ); ?>" class="mainwp-submenu"><?php esc_html_e( 'REST API', 'mainwp' ); ?></a>
217 + <a href="<?php echo esc_url( admin_url( 'admin.php?page=RESTAPI' ) ); // NOSONAR. ?>" class="mainwp-submenu"><?php esc_html_e( 'API Access', 'mainwp' ); ?></a>
149 218 <?php
150 219 if ( isset( static::$subPages ) && is_array( static::$subPages ) && ! empty( static::$subPages ) ) {
151 220 foreach ( static::$subPages as $subPage ) {
152 221 if ( MainWP_Menu::is_disable_menu_item( 3, 'RESTAPI' . $subPage['slug'] ) ) {
@@ -171,39 +240,98 @@
171 240 *
172 241 * @param array $subPages SubPages Array.
173 242 */
174 243 public static function init_left_menu( $subPages = array() ) {
244 + // Determine if the user has permission to view either REST API or Application Passwords.
245 + $is_can_view_rest_api = static::can_access_rest_api();
246 + $is_can_view_application_passwords = static::$application_passwords->can_access_application_passwords();
247 + if ( ! $is_can_view_rest_api && ! $is_can_view_application_passwords ) {
248 + return;
249 + }
175 250
251 + $api_keys_title = esc_html__( 'API Keys', 'mainwp' ); // NOSONAR.
252 + $application_passwords_title = esc_html__( 'Application Passwords', 'mainwp' );
253 + $application_passwords_url = 'admin.php?page=ApplicationPasswords';
254 +
255 + // Determine the landing href for the top-level API Access item based on permissions.
256 + $root_href = 'admin.php?page=RESTAPI';
257 + if ( ! $is_can_view_rest_api && $is_can_view_application_passwords ) {
258 + $root_href = $application_passwords_url;
259 + }
260 +
176 261 MainWP_Menu::add_left_menu(
177 262 array(
178 - 'title' => esc_html__( 'REST API', 'mainwp' ),
263 + 'title' => esc_html__( 'Access', 'mainwp' ),
179 264 'parent_key' => 'mainwp_tab',
180 265 'slug' => 'RESTAPI',
181 - 'href' => 'admin.php?page=RESTAPI',
266 + 'href' => $root_href,
267 + 'id' => 'mainwp-restapi-active-item',
182 268 'icon' => '<div class="mainwp-api-icon">API</div>',
183 269 ),
184 270 0
185 271 );
186 272
187 - $init_sub_subleftmenu = array(
188 - array(
189 - 'title' => esc_html__( 'Manage API Keys', 'mainwp' ),
190 - 'parent_key' => 'RESTAPI',
273 + // API Keys.
274 + $sub_rest_api = array();
275 + if ( $is_can_view_rest_api ) {
276 + // Second-level groups (dropdowns) under API Access.
277 + MainWP_Menu::add_left_menu(
278 + array(
279 + 'title' => $api_keys_title,
280 + 'parent_key' => 'RESTAPI',
281 + 'slug' => 'RESTAPIKeys',
282 + 'href' => 'admin.php?page=RESTAPI',
283 + ),
284 + 1
285 + );
286 + $sub_rest_api[] = array(
287 + 'title' => $api_keys_title,
288 + 'parent_key' => 'RESTAPIKeys',
191 289 'href' => 'admin.php?page=RESTAPI',
192 290 'slug' => 'RESTAPI',
193 - 'right' => 'manage_restapi',
194 - ),
291 + 'right' => '',
292 + );
195 293
196 - array(
197 - 'title' => esc_html__( 'Add API Keys', 'mainwp' ),
198 - 'parent_key' => 'RESTAPI',
199 - 'href' => 'admin.php?page=AddApiKeys',
200 - 'slug' => 'AddApiKeys',
294 + if ( static::can_create_rest_api_keys() ) {
295 + $sub_rest_api[] = array(
296 + 'title' => esc_html__( 'Add API Keys', 'mainwp' ),
297 + 'parent_key' => 'RESTAPIKeys',
298 + 'href' => 'admin.php?page=AddApiKeys',
299 + 'slug' => 'AddApiKeys',
300 + 'right' => '',
301 + );
302 + }
303 + }
304 +
305 + // Application Passwords.
306 + $sub_application_passwords = array();
307 + if ( $is_can_view_application_passwords ) {
308 + MainWP_Menu::add_left_menu(
309 + array(
310 + 'title' => $application_passwords_title,
311 + 'parent_key' => 'RESTAPI',
312 + 'slug' => 'RESTAPIAppPasswords',
313 + 'href' => $application_passwords_url,
314 + ),
315 + 1
316 + );
317 + $sub_application_passwords[] = array(
318 + 'title' => $application_passwords_title,
319 + 'parent_key' => 'RESTAPIAppPasswords',
320 + 'href' => $application_passwords_url,
321 + 'slug' => 'ApplicationPasswords',
201 322 'right' => '',
202 - ),
323 + );
324 + }
325 +
326 + // Third-level items (actual links).
327 + $init_sub_subleftmenu = array_merge(
328 + $sub_rest_api,
329 + $sub_application_passwords
203 330 );
204 331
205 - MainWP_Menu::init_subpages_left_menu( $subPages, $init_sub_subleftmenu, 'RESTAPI', 'RESTAPI' );
332 + // If any dynamic subpages exist, attach them under API Keys group by default.
333 + MainWP_Menu::init_subpages_left_menu( $subPages, $init_sub_subleftmenu, 'RESTAPIKeys', 'RESTAPI' );
206 334 foreach ( $init_sub_subleftmenu as $item ) {
207 335 if ( MainWP_Menu::is_disable_menu_item( 3, $item['slug'] ) ) {
208 336 continue;
209 337 }
@@ -225,8 +353,13 @@
225 353 $submit = true;
226 354 }
227 355
228 356 if ( $submit && isset( $_POST['wp_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_nonce'] ), 'RESTAPI' ) ) {
357 + // Only users with explicit create permission can create new API keys.
358 + if ( ! static::can_create_rest_api_keys() ) {
359 + wp_safe_redirect( admin_url( 'admin.php?page=RESTAPI' ) );
360 + exit;
361 + }
229 362 $all_keys = static::check_rest_api_updates();
230 363
231 364 if ( ! is_array( $all_keys ) ) {
232 365 $all_keys = array();
@@ -257,17 +390,45 @@
257 390 // compatible with version 2.
258 391 $scope = 'read';
259 392 if ( ! empty( $pers ) ) {
260 393 $pers_list = explode( ',', $pers );
261 - if ( in_array( 'w', $pers_list ) && in_array( 'd', $pers_list ) ) {
394 + $has_read = in_array( 'r', $pers_list );
395 + $has_write = in_array( 'w', $pers_list ) || in_array( 'd', $pers_list );
396 +
397 + if ( $has_read && $has_write ) {
262 398 $scope = 'read_write';
263 - } elseif ( in_array( 'w', $pers_list ) ) {
399 + } elseif ( $has_write ) {
264 400 $scope = 'write';
401 + } elseif ( $has_read ) {
402 + $scope = 'read';
265 403 }
266 404 }
267 - MainWP_DB::instance()->insert_rest_api_key( $consumer_key, $consumer_secret, $scope, $desc, $enabled );
268 - // end.
269 - wp_safe_redirect( admin_url( 'admin.php?page=RESTAPI&message=created' ) ); //phpcs:ignore -- ok.
405 + $this->invalidate_warm_cache();
406 + $api_key = MainWP_DB::instance()->insert_rest_api_key( $consumer_key, $consumer_secret, $scope, $desc, $enabled );
407 + // insert_rest_api_key() returns false when wpdb->insert() fails.
408 + // Without this guard the redirect would still claim success even
409 + // though no row was persisted.
410 + if ( ! is_array( $api_key ) || empty( $api_key['key_id'] ) ) {
411 + wp_safe_redirect(
412 + add_query_arg(
413 + array(
414 + 'page' => 'RESTAPI',
415 + 'message' => 'create_failed',
416 + ),
417 + admin_url( 'admin.php' )
418 + )
419 + );
420 + exit();
421 + }
422 + wp_safe_redirect(
423 + add_query_arg(
424 + array(
425 + 'page' => 'RESTAPI',
426 + 'message' => 'created',
427 + ),
428 + admin_url( 'admin.php' )
429 + )
430 + );
270 431 exit();
271 432 }
272 433 // phpcs:enable
273 434 }
@@ -281,8 +442,13 @@
281 442
282 443 $edit_id = isset( $_POST['editkey_id'] ) ? sanitize_text_field( wp_unslash( $_POST['editkey_id'] ) ) : false;
283 444
284 445 if ( isset( $_POST['submit'] ) && ! empty( $edit_id ) && isset( $_POST['edit_key_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['edit_key_nonce'] ), 'edit-key-nonce-' . $edit_id ) ) {
446 + // Only users with explicit edit permission can update API keys.
447 + if ( ! static::can_edit_rest_api_keys() ) {
448 + wp_safe_redirect( admin_url( 'admin.php?page=RESTAPI' ) );
449 + exit();
450 + }
285 451
286 452 $save = false;
287 453 $updated = false;
288 454 if ( ! empty( $edit_id ) ) {
@@ -293,12 +459,17 @@
293 459 $pers = ! empty( $_POST['mainwp_rest_api_key_edit_pers'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_edit_pers'] ) ) : '';
294 460 $scope = 'read';
295 461 if ( ! empty( $pers ) ) {
296 462 $pers_list = explode( ',', $pers );
297 - if ( in_array( 'w', $pers_list ) && in_array( 'r', $pers_list ) ) {
463 + $has_read = in_array( 'r', $pers_list );
464 + $has_write = in_array( 'w', $pers_list ) || in_array( 'd', $pers_list );
465 +
466 + if ( $has_read && $has_write ) {
298 467 $scope = 'read_write';
299 - } elseif ( in_array( 'w', $pers_list ) ) {
468 + } elseif ( $has_write ) {
300 469 $scope = 'write';
470 + } elseif ( $has_read ) {
471 + $scope = 'read';
301 472 }
302 473 }
303 474
304 475 MainWP_DB::instance()->update_rest_api_key( $key_id, $scope, $desc, $enabled );
@@ -333,8 +504,11 @@
333 504 $msg = '';
334 505 if ( $updated ) {
335 506 $msg = '&message=saved';
336 507 }
508 +
509 + $this->invalidate_warm_cache();
510 +
337 511 wp_safe_redirect( admin_url( 'admin.php?page=RESTAPI' . $msg ) ); //phpcs:ignore -- ok.
338 512 exit();
339 513 }
340 514 }
@@ -339,8 +513,15 @@
339 513 }
340 514 }
341 515
342 516 /**
517 + * Method invalidate_warm_cache()
518 + */
519 + public function invalidate_warm_cache() {
520 + MainWP_Cache_Warm_Helper::invalidate_manage_pages( array( 'RESTAPI' ) );
521 + }
522 +
523 + /**
343 524 * Method ajax_rest_api_remove_keys()
344 525 *
345 526 * Remove API Key.
346 527 */
@@ -345,8 +526,18 @@
345 526 * Remove API Key.
346 527 */
347 528 public function ajax_rest_api_remove_keys() { //phpcs:ignore -- NOSONAR - complex.
348 529 MainWP_Post_Handler::instance()->check_security( 'mainwp_rest_api_remove_keys' );
530 + // Require delete permission to remove API keys.
531 + if ( ! static::can_delete_rest_api_keys() ) {
532 + echo wp_json_encode(
533 + array(
534 + 'success' => false,
535 + 'error' => esc_html__( 'You are not allowed to delete REST API keys.', 'mainwp' ),
536 + )
537 + );
538 + exit();
539 + }
349 540 $ret = array( 'success' => false );
350 541 $cons_key_id = isset( $_POST['keyId'] ) ? urldecode( wp_unslash( $_POST['keyId'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
351 542 $is_v2 = isset( $_POST['api_ver'] ) && 'v2' === wp_unslash( $_POST['api_ver'] ) ? true : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
352 543
@@ -368,8 +559,9 @@
368 559 }
369 560 }
370 561 $ret['success'] = 'SUCCESS';
371 562 $ret['result'] = esc_html__( 'REST API Key deleted successfully.', 'mainwp' );
563 + $this->invalidate_warm_cache();
372 564 } else {
373 565 $ret['error'] = esc_html__( 'REST API Key ID empty.', 'mainwp' );
374 566 }
375 567 echo wp_json_encode( $ret );
@@ -388,9 +580,9 @@
388 580 */
389 581 public static function render_header( $shownPage = '' ) { // phpcs:ignore -- NOSONAR - complex.
390 582
391 583 $params = array(
392 - 'title' => esc_html__( 'REST API', 'mainwp' ),
584 + 'title' => esc_html__( 'API Access', 'mainwp' ),
393 585 );
394 586
395 587 MainWP_UI::render_top_header( $params );
396 588
@@ -395,17 +587,19 @@
395 587 MainWP_UI::render_top_header( $params );
396 588
397 589 $renderItems = array();
398 590
399 - if ( mainwp_current_user_have_right( 'dashboard', 'manage_restapi' ) ) {
591 + // Show API Keys tab if user can view REST API section (any of manage/create/edit/delete).
592 + if ( static::can_access_rest_api() ) {
400 593 $renderItems[] = array(
401 - 'title' => esc_html__( 'Manage API Keys', 'mainwp' ),
594 + 'title' => esc_html__( 'API Keys', 'mainwp' ),
402 595 'href' => 'admin.php?page=RESTAPI',
403 596 'active' => ( '' === $shownPage ) ? true : false,
404 597 );
405 598 }
406 599
407 - if ( ! MainWP_Menu::is_disable_menu_item( 3, 'AddApiKeys' ) ) {
600 + // Show Add API Keys tab if user can create or edit REST API keys or has manage permission.
601 + if ( ! MainWP_Menu::is_disable_menu_item( 3, 'AddApiKeys' ) && ( static::can_create_rest_api_keys() || static::can_edit_rest_api_keys() ) ) {
408 602 if ( isset( $_GET['editkey'] ) && ! empty( $_GET['editkey'] ) && isset( $_GET['_opennonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_opennonce'] ), 'mainwp-admin-nonce' ) ) {
409 603 // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
410 604 $ver = isset( $_GET['rest_ver'] ) && ! empty( $_GET['rest_ver'] ) ? '&rest_ver=' . intval( $_GET['rest_ver'] ) : '';
411 605 $renderItems[] = array(
@@ -420,8 +614,20 @@
420 614 'active' => ( 'Settings' === $shownPage ) ? true : false,
421 615 );
422 616 }
423 617
618 + if ( ! MainWP_Menu::is_disable_menu_item( 3, 'ApplicationPasswords' ) && ( static::$application_passwords->can_view_manage_application_passwords() ||
619 + static::$application_passwords->can_view_all_application_passwords() ||
620 + static::$application_passwords->can_create_application_passwords() ||
621 + static::$application_passwords->can_delete_application_passwords() ) ) {
622 +
623 + $renderItems[] = array(
624 + 'title' => esc_html__( 'Application Passwords', 'mainwp' ),
625 + 'href' => 'admin.php?page=ApplicationPasswords',
626 + 'active' => ( 'Application Passwords' === $shownPage ) ? true : false,
627 + );
628 + }
629 +
424 630 if ( isset( static::$subPages ) && is_array( static::$subPages ) ) {
425 631 foreach ( static::$subPages as $subPage ) {
426 632 if ( MainWP_Menu::is_disable_menu_item( 3, 'RESTAPI' . $subPage['slug'] ) ) {
427 633 continue;
@@ -443,143 +649,87 @@
443 649 public static function render_footer() {
444 650 echo '</div>';
445 651 }
446 652
447 -
448 -
449 - /** Render REST API SubPage */
450 - public static function render_api_keys_v2_table() { // phpcs:ignore -- NOSONAR - complex.
451 - $all_keys_v2 = MainWP_DB::instance()->get_rest_api_keys();
653 + /**
654 + * Render a friendly permission denied UI inside MainWP layout.
655 + *
656 + * @param string $title Page title to show in header.
657 + * @param string $message Message to show in the body.
658 + */
659 + protected static function render_permission_denied_ui( $title, $message ) {
660 + $params = array( 'title' => $title );
661 + MainWP_UI::render_top_header( $params );
452 662 ?>
453 - <table id="mainwp-rest-api-keys-v2-table" class="ui unstackable table">
454 - <thead>
455 - <tr>
456 - <th scope="col" class="no-sort collapsing check-column"><span class="ui checkbox"><input aria-label="<?php esc_attr_e( 'Select all REST API keys', 'mainwp' ); ?>" id="cb-select-all-top" type="checkbox" /></span></th>
457 - <th scope="col" ><?php esc_html_e( 'API Key (v2)', 'mainwp' ); ?></th>
458 - <th scope="col" class="collapsing"><?php esc_html_e( 'Status', 'mainwp' ); ?></th>
459 - <th scope="col" class="collapsing"><?php esc_html_e( 'Permissions', 'mainwp' ); ?></th>
460 - <th scope="col" class="no-sort collapsing"><?php esc_html_e( 'API key ending in', 'mainwp' ); ?></th>
461 - <th scope="col"><?php esc_html_e( 'Last access', 'mainwp' ); ?></th>
462 - <th scope="col" class="no-sort collapsing"></th>
463 - </tr>
464 - </thead>
465 - <tbody id="mainwp-rest-api-v2-body-table" class="mainwp-rest-api-body-table-manage">
466 - <?php
467 - if ( is_array( $all_keys_v2 ) ) {
468 - foreach ( $all_keys_v2 as $item ) {
469 - $ending = $item->truncated_key;
470 - $desc = ! empty( $item->description ) ? esc_html( $item->description ) : 'N/A';
471 - $enabled = $item->enabled ? true : false;
472 - $key_id = $item->key_id;
473 -
474 - $pers_title = array();
475 - $per = $item->permissions;
476 - if ( 'read' === $per ) {
477 - $pers_title[] = esc_html__( 'Read', 'mainwp' );
478 - }
479 - if ( 'write' === $per ) {
480 - $pers_title[] = esc_html__( 'Write', 'mainwp' );
481 - }
482 - if ( 'read_write' === $per ) {
483 - $pers_title[] = esc_html__( 'Read', 'mainwp' );
484 - $pers_title[] = esc_html__( 'Write', 'mainwp' );
485 - }
486 - ?>
487 - <tr key-ck-id="<?php echo intval( $key_id ); ?>">
488 - <td class="check-column">
489 - <div class="ui checkbox">
490 - <input type="checkbox" aria-label="<?php echo esc_attr__( 'Select API key described as: ', 'mainwp' ) . esc_html( $desc ); ?>" value="<?php echo intval( $key_id ); ?>" name=""/>
491 - </div>
492 - </td>
493 - <td><a href="admin.php?page=AddApiKeys&editkey=<?php echo intval( $key_id ); ?>&rest_ver=2&_opennonce=<?php echo esc_html( wp_create_nonce( 'mainwp-admin-nonce' ) ); ?>"><?php echo esc_html( $desc ); ?></a></td>
494 - <td><?php echo $enabled ? '<span class="ui green fluid label">' . esc_html__( 'Enabled', 'mainwp' ) . '</span>' : '<span class="ui gray fluid label">' . esc_html__( 'Disabled', 'mainwp' ) . '</span>'; ?></td>
495 - <td><?php echo ! empty( $pers_title ) ? implode( ', ', $pers_title ) : 'N/A'; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
496 - <td><code><?php echo esc_html( '...' . $ending ); // phpcs:ignore WordPress.Security.EscapeOutput ?></code></td>
497 - <td sort-value="<?php echo ! empty( $item->last_access ) ? strtotime( $item->last_access ) : 0; ?>"><?php echo ! empty( $item->last_access ) ? MainWP_Utility::format_timestamp( MainWP_Utility::get_timestamp( strtotime( $item->last_access ) ) ) : 'N/A'; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
498 - <td class="collapsing">
499 - <a class="ui green basic mini button" href="admin.php?page=AddApiKeys&editkey=<?php echo intval( $key_id ); ?>&rest_ver=2&_opennonce=<?php echo esc_html( wp_create_nonce( 'mainwp-admin-nonce' ) ); ?>"><?php esc_html_e( 'Edit', 'mainwp' ); ?></a>
500 - <a class="ui mini button" href="javascript:void(0)" onclick="mainwp_restapi_remove_key_confirm(jQuery(this).closest('tr').find('.check-column INPUT:checkbox'));" ><?php esc_html_e( 'Delete', 'mainwp' ); ?></a>
501 - </td>
502 - </tr>
503 - <?php
504 - }
505 - }
506 - ?>
507 - </tbody>
508 - </table>
509 - <script type="text/javascript">
510 - var responsive = true;
511 - if( jQuery( window ).width() > 1140 ) {
512 - responsive = false;
513 - }
514 - jQuery( document ).ready( function( $ ) {
515 - try {
516 - jQuery( '#mainwp-rest-api-keys-v2-table' ).DataTable( {
517 - "lengthMenu": [ [5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, "All"] ],
518 - "stateSave" : true,
519 - "order" : [ [1, 'asc'] ],
520 - "columnDefs": [ {
521 - "targets": 'no-sort',
522 - "orderable": false
523 - } ],
524 - "responsive": responsive,
525 - "preDrawCallback": function() {
526 - jQuery( '#mainwp-rest-api-keys-v2-table .ui.dropdown' ).dropdown();
527 - jQuery('#mainwp-rest-api-keys-v2-table .ui.checkbox').checkbox();
528 - mainwp_datatable_fix_menu_overflow();
529 - mainwp_table_check_columns_init(); // ajax: to fix checkbox all.
530 - }
531 - } );
532 - mainwp_datatable_fix_menu_overflow();
533 - } catch(err) {
534 - // to fix js error.
535 - }
536 - });
537 - </script>
538 -
663 + <div class="ui segment">
664 + <div class="ui negative message">
665 + <i class="ban icon"></i>
666 + <div class="content">
667 + <div class="header"><?php echo esc_html( $title ); ?></div>
668 + <p><?php echo esc_html( $message ); ?></p>
669 + </div>
670 + </div>
671 + <div class="ui divider"></div>
672 + <div class="ui two column stackable grid">
673 + <div class="column">
674 + <?php if ( current_user_can( 'manage_options' ) ) : ?>
675 + <a class="ui small basic button" href="<?php echo esc_url( admin_url( 'admin.php?page=RESTAPI' ) ); ?>">
676 + <i class="key icon"></i> <?php esc_html_e( 'API Keys', 'mainwp' ); ?>
677 + </a>
678 + <?php endif; ?>
679 + </div>
680 + <div class="column right aligned">
681 + <?php if ( static::$application_passwords->can_access_application_passwords() ) : ?>
682 + <a class="ui small green button"
683 + href="<?php echo esc_url( admin_url( 'admin.php?page=ApplicationPasswords' ) ); ?>">
684 + <i class="lock icon"></i> <?php esc_html_e( 'Go to Application Passwords', 'mainwp' ); ?>
685 + </a>
686 + <?php else : ?>
687 + <a class="ui small button" href="<?php echo esc_url( admin_url( 'admin.php?page=mainwp_tab' ) ); ?>">
688 + <i class="home icon"></i> <?php esc_html_e( 'Back to Dashboard', 'mainwp' ); ?>
689 + </a>
690 + <?php endif; ?>
691 + </div>
692 + </div>
693 + </div>
539 694 <?php
695 + static::render_footer();
540 696 }
541 697
698 + /**
699 + * Method render_api_keys_v1_table.
700 + *
701 + * @return void
702 + */
703 + public static function render_api_keys_v1_table() { //phpcs:ignore -- NOSONAR - complex.
704 + $all_keys = static::check_rest_api_updates();
705 + $can_edit_keys = static::can_edit_rest_api_keys();
706 + $can_delete_keys = static::can_delete_rest_api_keys();
542 707
543 - /** Render REST API SubPage */
544 - public static function render_all_api_keys() { // phpcs:ignore -- NOSONAR - complex.
545 - if ( ! mainwp_current_user_have_right( 'dashboard', 'manage_dashboard_restapi' ) ) {
546 - mainwp_do_not_have_permissions( esc_html__( 'manage dashboard REST API', 'mainwp' ) );
547 - return;
708 + if ( ! is_array( $all_keys ) ) {
709 + $all_keys = array();
548 710 }
549 - static::render_header();
550 - static::render_table_top();
551 - if ( ! static::check_rest_api_enabled() ) {
711 +
712 + if ( ! empty( $all_keys ) ) {
713 + $write_delete = esc_html__( 'Write & Delete', 'mainwp' ); // NOSONAR - ok.
552 714 ?>
553 - <div class="ui message yellow"><?php printf( esc_html__( 'It seems the WordPress REST API is currently disabled on your site. MainWP REST API requires the WordPress REST API to function properly. Please enable it to ensure smooth operation. Need help? %sClick here for a guide%s.', 'mainwp' ), '<a href="https://kb.mainwp.com/docs/wordpress-rest-api-does-not-respond/" target="_blank">', '</a> <i class="external alternate icon"></i>' ); ?></div>
554 - <?php
555 - }
556 - ?>
557 - <div id="mainwp-rest-api-keys" class="ui segment">
558 - <div class="ui message" id="mainwp-message-zone-apikeys" style="display:none;"></div>
559 - <?php static::show_messages(); ?>
560 - <?php
561 - static::render_api_keys_v2_table();
562 715
563 - $all_keys = static::check_rest_api_updates();
564 - if ( ! is_array( $all_keys ) ) {
565 - $all_keys = array();
566 - }
567 -
568 - if ( ! empty( $all_keys ) ) {
569 - ?>
570 - <table id="mainwp-rest-api-keys-table" class="ui unstackable table">
716 + <table id="mainwp-rest-api-keys-table" class="ui unstackable single linetable">
571 717 <thead>
572 718 <tr>
573 - <th scope="col" class="no-sort collapsing check-column"><span class="ui checkbox"><input aria-label="<?php esc_attr_e( 'Select all REST API keys', 'mainwp' ); ?>" id="cb-select-all-top" type="checkbox" /></span></th>
574 - <th scope="col" ><?php esc_html_e( 'API Key (v1)', 'mainwp' ); ?></th>
719 + <th scope="col" class="no-sort collapsing check-column"><span class="ui checkbox"><input
720 + aria-label="<?php esc_attr_e( 'Select all REST API keys', 'mainwp' ); ?>" id="cb-select-all-top"
721 + type="checkbox" /></span></th>
575 722 <th scope="col" class="collapsing"><?php esc_html_e( 'Status', 'mainwp' ); ?></th>
723 + <th scope="col"><?php esc_html_e( 'API Key', 'mainwp' ); ?></th>
576 724 <th scope="col" class="collapsing"><?php esc_html_e( 'Permissions', 'mainwp' ); ?></th>
577 725 <th scope="col" class="no-sort collapsing"><?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></th>
578 - <th scope="col" class="no-sort collapsing"></th>
726 + <?php if ( $can_edit_keys || $can_delete_keys ) : ?>
727 + <th scope="col" class="no-sort collapsing"></th>
728 + <?php endif; ?>
579 729 </tr>
580 730 </thead>
581 - <tbody id="mainwp-rest-api-body-table" class="mainwp-rest-api-body-table-manage">
731 + <tbody id="mainwp-rest-api-body-table" class="mainwp-rest-api-body-table-manage">
582 732 <?php
583 733 foreach ( $all_keys as $ck => $item ) {
584 734 if ( ! is_array( $item ) ) {
585 735 continue;
@@ -612,84 +762,365 @@
612 762 }
613 763 }
614 764
615 765 ?>
616 - <tr key-ck-id="<?php echo esc_html( $endcoded_ck ); ?>">
766 + <tr key-ck-id="<?php echo esc_attr( $endcoded_ck ); ?>">
617 767 <td class="check-column">
618 768 <div class="ui checkbox">
619 - <input type="checkbox" aria-label="<?php echo esc_attr__( 'Select API key described as: ', 'mainwp' ) . esc_html( $desc ); ?>" value="<?php echo esc_html( $endcoded_ck ); ?>" name=""/>
769 + <input type="checkbox"
770 + aria-label="<?php echo esc_attr__( 'Select API key described as: ', 'mainwp' ) . esc_html( $desc ); ?>"
771 + value="<?php echo esc_html( $endcoded_ck ); ?>" name="" />
620 772 </div>
621 773 </td>
622 - <td><a href="admin.php?page=AddApiKeys&editkey=<?php echo esc_html( $endcoded_ck ); ?>"><?php echo esc_html( $desc ); ?></a></td>
623 - <td><?php echo $enabled ? '<span class="ui green fluid label">' . esc_html__( 'Enabled', 'mainwp' ) . '</span>' : '<span class="ui gray fluid label">' . esc_html__( 'Disabled', 'mainwp' ) . '</span>'; ?></td>
624 - <td><?php echo ! empty( $pers_names ) ? implode( ', ', $pers_names ) : 'N/A'; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
625 - <td><code><?php echo esc_html( '...' . $ending ); // phpcs:ignore WordPress.Security.EscapeOutput ?></code></td>
626 - <td class="collapsing">
627 - <a class="ui green basic mini button" href="admin.php?page=AddApiKeys&editkey=<?php echo esc_html( $endcoded_ck ); ?>&_opennonce=<?php echo esc_html( wp_create_nonce( 'mainwp-admin-nonce' ) ); ?>"><?php esc_html_e( 'Edit', 'mainwp' ); ?></a>
628 - <a class="ui mini button" href="javascript:void(0)" onclick="mainwp_restapi_remove_key_confirm(jQuery(this).closest('tr').find('.check-column INPUT:checkbox'));" ><?php esc_html_e( 'Delete', 'mainwp' ); ?></a>
774 + <td><?php echo $enabled ? '<span class="ui green fluid label">' . esc_html__( 'Enabled', 'mainwp' ) . '</span>' : '<span class="ui gray fluid label">' . esc_html__( 'Disabled', 'mainwp' ) . '</span>'; ?>
629 775 </td>
776 + <td>
777 + <?php if ( $can_edit_keys ) : ?>
778 + <?php
779 + $url = add_query_arg(
780 + array(
781 + 'page' => 'AddApiKeys',
782 + 'editkey' => $endcoded_ck,
783 + '_opennonce' => wp_create_nonce( 'mainwp-admin-nonce' ),
784 + ),
785 + admin_url( 'admin.php' )
786 + );
787 + ?>
788 + <a href="<?php echo esc_url( $url ); ?>"><?php echo esc_html( $desc ); ?></a>
789 + <?php else : ?>
790 + <?php echo esc_html( $desc ); ?>
791 + <?php endif; ?>
792 + </td>
793 + <td><?php echo ! empty( $pers_names ) ? implode( ', ', $pers_names ) : 'N/A'; // phpcs:ignore WordPress.Security.EscapeOutput ?>
794 + </td>
795 + <td><code><?php echo esc_html( '...' . $ending ); // phpcs:ignore WordPress.Security.EscapeOutput ?></code>
796 + </td>
797 + <?php if ( $can_edit_keys || $can_delete_keys ) : ?>
798 + <td class="right aligned">
799 + <div class="ui right pointing dropdown" style="z-index:999">
800 + <i class="ellipsis vertical icon"></i>
801 + <div class="menu">
802 + <?php if ( $can_edit_keys ) : ?>
803 + <?php
804 + $url = add_query_arg(
805 + array(
806 + 'page' => 'AddApiKeys',
807 + 'editkey' => $endcoded_ck,
808 + '_opennonce' => wp_create_nonce( 'mainwp-admin-nonce' ),
809 + ),
810 + admin_url( 'admin.php' )
811 + );
812 + ?>
813 + <a class="item"
814 + href="<?php echo esc_url( $url ); ?>"><?php esc_html_e( 'Edit', 'mainwp' ); ?></a>
815 + <?php endif; ?>
816 + <?php if ( $can_delete_keys ) : ?>
817 + <a class="item" href="javascript:void(0)"
818 + onclick="mainwp_restapi_remove_key_confirm(jQuery(this).closest('tr').find('.check-column INPUT:checkbox'));"><?php esc_html_e( 'Delete', 'mainwp' ); ?></a>
819 + <?php endif; ?>
820 + </div>
821 + </div>
822 + </td>
823 + <?php endif; ?>
630 824 </tr>
631 - <?php
632 - }
633 - ?>
825 + <?php } ?>
634 826 </tbody>
635 827 </table>
636 - </div>
828 + <script type="text/javascript">
829 + var responsive = true;
830 + if (jQuery(window).width() > 1140) {
831 + responsive = false;
832 + }
833 + jQuery(document).ready(function ($) {
834 + try {
835 + jQuery('#mainwp-rest-api-keys-table').DataTable({
836 + "lengthMenu": [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, "All"]],
837 + "stateSave": true,
838 + "order": [[1, 'asc']],
839 + "columnDefs": [{
840 + "targets": 'no-sort',
841 + "orderable": false
842 + }],
843 + "responsive": responsive,
844 + "preDrawCallback": function () {
845 + jQuery('#mainwp-rest-api-keys-table .ui.dropdown').dropdown();
846 + jQuery('#mainwp-rest-api-keys-table .ui.checkbox').checkbox();
847 + mainwp_datatable_fix_menu_overflow('#mainwp-rest-api-keys-table', -70, 0);
848 + mainwp_table_check_columns_init(); // ajax: to fix checkbox all.
849 + }
850 + });
851 + mainwp_datatable_fix_menu_overflow('#mainwp-rest-api-keys-table', -70, 0);
852 + } catch {
853 + // to fix js error.
854 + }
855 + });
856 + </script>
857 + <?php
858 + }
859 + }
860 +
861 + /** Render REST API SubPage */
862 + public static function render_api_keys_v2_table() { // phpcs:ignore -- NOSONAR - complex.
863 + $all_keys_v2 = MainWP_DB::instance()->get_rest_api_keys();
864 + $can_edit_keys = static::can_edit_rest_api_keys();
865 + $can_delete_keys = static::can_delete_rest_api_keys();
866 + $el_id_cb_1 = 'cb-select-all-top';
867 + ?>
868 + <table id="mainwp-rest-api-keys-v2-table" class="ui unstackable single line table">
869 + <thead>
870 + <tr>
871 + <th scope="col" class="no-sort collapsing check-column">
872 + <span class="ui checkbox">
873 + <input
874 + aria-label="<?php esc_attr_e( 'Select all REST API keys', 'mainwp' ); ?>"
875 + id="<?php echo esc_attr( $el_id_cb_1 ); ?>" type="checkbox" />
876 + </span>
877 + </th>
878 + <th scope="col" class="collapsing"><?php esc_html_e( 'Status', 'mainwp' ); ?></th>
879 + <th scope="col"><?php esc_html_e( 'API Key (v2)', 'mainwp' ); ?></th>
880 + <th scope="col" class="collapsing"><?php esc_html_e( 'Permissions', 'mainwp' ); ?></th>
881 + <th scope="col" class="no-sort collapsing"><?php esc_html_e( 'API key ending in', 'mainwp' ); ?></th>
882 + <th scope="col" class="collapsing"><?php esc_html_e( 'Last access', 'mainwp' ); ?></th>
883 + <?php if ( $can_edit_keys || $can_delete_keys ) : ?>
884 + <th scope="col" class="no-sort collapsing"></th>
885 + <?php endif; ?>
886 + </tr>
887 + </thead>
888 + <tbody id="mainwp-rest-api-v2-body-table" class="mainwp-rest-api-body-table-manage">
889 + <?php
890 + if ( is_array( $all_keys_v2 ) ) :
891 + foreach ( $all_keys_v2 as $item ) :
892 + $ending = $item->truncated_key;
893 + $desc = ! empty( $item->description ) ? esc_html( $item->description ) : 'N/A';
894 + $enabled = $item->enabled ? true : false;
895 + $key_id = $item->key_id;
896 +
897 + $pers_title = array();
898 + $per = $item->permissions;
899 + if ( 'read' === $per ) {
900 + $pers_title[] = esc_html__( 'Read', 'mainwp' );
901 + }
902 + if ( 'write' === $per || 'delete' === $per ) {
903 + $pers_title[] = esc_html__( 'Write & Delete', 'mainwp' );
904 + }
905 + if ( 'read_write' === $per ) {
906 + $pers_title[] = esc_html__( 'Read', 'mainwp' );
907 + $pers_title[] = esc_html__( 'Write & Delete', 'mainwp' );
908 + }
909 + $url = '';
910 + if ( $can_edit_keys ) {
911 + $url = add_query_arg(
912 + array(
913 + 'page' => 'AddApiKeys',
914 + 'editkey' => intval( $key_id ),
915 + 'rest_ver' => 2,
916 + '_opennonce' => wp_create_nonce( 'mainwp-admin-nonce' ),
917 + ),
918 + admin_url( 'admin.php' )
919 + );
920 + }
921 + ?>
922 + <tr key-ck-id="<?php echo intval( $key_id ); ?>">
923 + <td class="check-column">
924 + <div class="ui checkbox">
925 + <input type="checkbox"
926 + aria-label="<?php echo esc_attr__( 'Select API key described as: ', 'mainwp' ) . esc_html( $desc ); ?>"
927 + value="<?php echo intval( $key_id ); ?>" name="" />
928 + </div>
929 + </td>
930 + <td><?php echo $enabled ? '<span class="ui green fluid label">' . esc_html__( 'Enabled', 'mainwp' ) . '</span>' : '<span class="ui gray fluid label">' . esc_html__( 'Disabled', 'mainwp' ) . '</span>'; ?>
931 + </td>
932 + <td>
933 + <?php if ( $can_edit_keys ) : ?>
934 + <a href="<?php echo esc_url( $url ); ?>"><?php echo esc_html( $desc ); ?></a>
935 + <?php else : ?>
936 + <?php echo esc_html( $desc ); ?>
937 + <?php endif; ?>
938 + </td>
939 + <td><?php echo ! empty( $pers_title ) ? implode( ', ', $pers_title ) : 'N/A'; // phpcs:ignore WordPress.Security.EscapeOutput ?>
940 + </td>
941 + <td><code><?php echo esc_html( '...' . $ending ); // phpcs:ignore WordPress.Security.EscapeOutput ?></code>
942 + </td>
943 + <td data-order="<?php echo ! empty( $item->last_access ) ? esc_attr( strtotime( $item->last_access ) ) : 0; ?>">
944 + <?php echo ! empty( $item->last_access ) ? '<span data-tooltip="' . esc_attr( MainWP_Utility::format_timestamp( MainWP_Utility::get_timestamp( strtotime( $item->last_access ) ) ) ) . '" data-position="left center" data-inverted="">' . esc_html( MainWP_Utility::time_elapsed_string( strtotime( $item->last_access ) ) ) . '</span>' : 'N/A'; // phpcs:ignore WordPress.Security.EscapeOutput ?>
945 + </td>
946 + <?php if ( $can_edit_keys || $can_delete_keys ) : ?>
947 + <td class="right aligned">
948 + <div class="ui right pointing dropdown" style="z-index:999">
949 + <i class="ellipsis vertical icon"></i>
950 + <div class="menu">
951 + <?php if ( $can_edit_keys ) : ?>
952 + <a class="item"
953 + href="<?php echo esc_url( $url ); ?>"><?php esc_html_e( 'Edit', 'mainwp' ); ?></a>
954 + <?php endif; ?>
955 + <?php if ( $can_delete_keys ) : ?>
956 + <a class="item" href="javascript:void(0)"
957 + onclick="mainwp_restapi_remove_key_confirm(jQuery(this).closest('tr').find('.check-column INPUT:checkbox'));"><?php esc_html_e( 'Delete', 'mainwp' ); ?></a>
958 + <?php endif; ?>
959 + </div>
960 + </div>
961 + </td>
962 + <?php endif; ?>
963 + </tr>
964 + <?php
965 + endforeach;
966 + endif;
967 + ?>
968 + </tbody>
969 + </table>
637 970 <script type="text/javascript">
638 971 var responsive = true;
639 - if( jQuery( window ).width() > 1140 ) {
972 + if (jQuery(window).width() > 1140) {
640 973 responsive = false;
641 974 }
642 - jQuery( document ).ready( function( $ ) {
975 + jQuery(document).ready(function ($) {
643 976 try {
644 - jQuery( '#mainwp-rest-api-keys-table' ).DataTable( {
645 - "lengthMenu": [ [5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, "All"] ],
646 - "stateSave" : true,
647 - "order" : [ [1, 'asc'] ],
648 - "columnDefs": [ {
977 + jQuery('#mainwp-rest-api-keys-v2-table').DataTable({
978 + "lengthMenu": [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, "All"]],
979 + "stateSave": true,
980 + "order": [[1, 'asc']],
981 + "columnDefs": [{
649 982 "targets": 'no-sort',
650 983 "orderable": false
651 - } ],
984 + }],
652 985 "responsive": responsive,
653 - "preDrawCallback": function() {
654 - jQuery( '#mainwp-rest-api-keys-table .ui.dropdown' ).dropdown();
655 - jQuery('#mainwp-rest-api-keys-table .ui.checkbox').checkbox();
656 - mainwp_datatable_fix_menu_overflow();
986 + "preDrawCallback": function () {
987 + jQuery('#mainwp-rest-api-keys-v2-table .ui.dropdown').dropdown();
988 + jQuery('#mainwp-rest-api-keys-v2-table .ui.checkbox').checkbox();
989 + mainwp_datatable_fix_menu_overflow('#mainwp-rest-api-keys-v2-table', -70, 0);
657 990 mainwp_table_check_columns_init(); // ajax: to fix checkbox all.
658 991 }
659 - } );
660 - mainwp_datatable_fix_menu_overflow();
661 - } catch(err) {
992 + });
993 + mainwp_datatable_fix_menu_overflow('#mainwp-rest-api-keys-v2-table', -70, 0);
994 + } catch {
662 995 // to fix js error.
663 996 }
664 997 });
665 998 </script>
999 +
1000 + <?php
1001 + }
1002 +
1003 + /**
1004 + * Render All API Keys
1005 + */
1006 + public static function render_all_api_keys() { // phpcs:ignore -- NOSONAR - complex.
1007 + // Allow page if user can view REST API (any of manage/create/edit/delete).
1008 + if ( ! static::can_access_rest_api() ) {
1009 + static::render_permission_denied_ui(
1010 + esc_html__( 'API Access', 'mainwp' ),
1011 + esc_html__( 'You do not have permission to view the REST API. If you need access to this page please contact the dashboard administrator.', 'mainwp' )
1012 + );
1013 + return;
1014 + }
1015 + $all_keys = static::check_rest_api_updates();
1016 + if ( ! is_array( $all_keys ) ) {
1017 + $all_keys = array();
1018 + }
1019 + $all_keys_v2 = MainWP_DB::instance()->get_rest_api_keys();
1020 + $has_no_api_keys = empty( $all_keys ) && empty( $all_keys_v2 );
1021 + static::render_header();
1022 + static::render_table_top( $has_no_api_keys );
1023 + if ( ! static::check_rest_api_enabled() ) {
1024 + ?>
1025 + <div class="ui message yellow">
666 1026 <?php
667 - } // end if.
668 - static::render_footer();
1027 + // translators: 1: Opening anchor tag. 2: Closing anchor tag.
1028 + printf( esc_html__( 'It seems the WordPress REST API is currently disabled on your site. MainWP REST API requires the WordPress REST API to function properly. Please enable it to ensure smooth operation. Need help? %1$sClick here for a guide%2$s.', 'mainwp' ), '<a href="https://docs.mainwp.com/troubleshooting/wordpress-rest-api-does-not-respond" target="_blank">', '</a> <i class="external alternate icon"></i>' );
1029 + ?>
1030 + </div>
1031 + <?php
1032 + }
1033 + ?>
1034 + <div id="mainwp-rest-api-keys" class="ui padded segment">
1035 + <?php if ( $has_no_api_keys ) : ?>
1036 + <?php
1037 + MainWP_UI::render_empty_page_placeholder(
1038 + esc_html__( 'No API Keys yet', 'mainwp' ),
1039 + esc_html__( 'Create an API key to integrate MainWP with your custom tools, automations, and external platforms through the REST API.', 'mainwp' ),
1040 + '<em data-emoji=":key:" class="big"></em>'
1041 + );
1042 + ?>
1043 + <?php if ( static::can_create_rest_api_keys() ) : ?>
1044 + <div class="ui center aligned basic segment">
1045 + <a href="<?php echo esc_url( admin_url( 'admin.php?page=AddApiKeys' ) ); ?>"
1046 + class="ui green button"><?php esc_html_e( 'Create New API Key', 'mainwp' ); ?></a>
1047 + </div>
1048 + <?php endif; ?>
1049 + <?php endif; ?>
1050 +
1051 +
1052 + <div class="ui message" id="mainwp-message-zone-apikeys" style="display:none;"></div>
1053 + <?php static::show_messages(); ?>
1054 + <?php if ( ! $has_no_api_keys ) : ?>
1055 + <div class="ui basic accordion mainwp-blank-accordion" id="mainwp-rest-api-keys-accordion">
1056 + <h2 class="ui dividing header active title">
1057 + <i class="right dropdown icon"></i>
1058 + <?php esc_html_e( 'MainWP REST API v2 API Keys', 'mainwp' ); ?>
1059 + <div class="sub header">
1060 + <?php esc_html_e( 'Generate secure API Keys to connect your MainWP Dashboard with trusted tools and automations.', 'mainwp' ); ?>
1061 + </div>
1062 + </h2>
1063 + <div class="content active">
1064 + <?php static::render_api_keys_v2_table(); ?>
1065 + </div>
1066 + <?php if ( ! empty( $all_keys ) ) : ?>
1067 + <h2 class="ui dividing header title">
1068 + <i class="right dropdown icon"></i>
1069 + <?php esc_html_e( 'MainWP REST API v1 API Keys (Legacy)', 'mainwp' ); ?>
1070 + <div class="sub header">
1071 + <?php esc_html_e( 'Legacy API keys for older integrations. We recommend switching to v2 for better security and performance.', 'mainwp' ); ?>
1072 + </div>
1073 + </h2>
1074 + <div class="content">
1075 + <?php static::render_api_keys_v1_table(); ?>
1076 + </div>
1077 + <?php endif; ?>
1078 + </div>
1079 + <script>
1080 + jQuery(document).ready(function () {
1081 + jQuery('#mainwp-rest-api-keys-accordion').accordion({
1082 + exclusive: false,
1083 + });
1084 + });
1085 + </script>
1086 + <?php endif; ?>
1087 + </div>
1088 + <?php
1089 + static::render_footer();
669 1090 }
670 1091
671 -
672 1092 /**
673 1093 * Render table top.
1094 + *
1095 + * @param bool $has_no_api_keys Whether there are no API keys.
674 1096 */
675 - public static function render_table_top() {
1097 + public static function render_table_top( $has_no_api_keys = false ) {
1098 + if ( $has_no_api_keys ) {
1099 + return;
1100 + }
676 1101 ?>
677 - <div class="mainwp-actions-bar">
1102 + <div class="mainwp-sub-header">
678 1103 <div class="ui grid">
679 1104 <div class="equal width row ui mini form">
680 1105 <div class="middle aligned column">
681 - <div id="mainwp-rest-api-bulk-actions-menu" class="ui selection dropdown">
682 - <div class="default text"><?php esc_html_e( 'Bulk actions', 'mainwp' ); ?></div>
683 - <i class="dropdown icon"></i>
684 - <div class="menu">
685 - <div class="item" data-value="delete"><?php esc_html_e( 'Delete', 'mainwp' ); ?></div>
1106 + <?php if ( ! $has_no_api_keys && static::can_delete_rest_api_keys() ) : ?>
1107 + <div id="mainwp-rest-api-bulk-actions-menu" class="ui selection dropdown disabled">
1108 + <div class="default text"><?php esc_html_e( 'Bulk actions', 'mainwp' ); ?></div>
1109 + <i class="dropdown icon"></i>
1110 + <div class="menu">
1111 + <div class="item" data-value="delete"><?php esc_html_e( 'Delete', 'mainwp' ); ?></div>
1112 + </div>
686 1113 </div>
687 - </div>
688 - <button class="ui mini basic button" id="mainwp-do-rest-api-bulk-actions"><?php esc_html_e( 'Apply', 'mainwp' ); ?></button>
1114 + <button class="ui mini basic button disabled"
1115 + id="mainwp-do-rest-api-bulk-actions"><?php esc_html_e( 'Apply', 'mainwp' ); ?></button>
1116 + <?php endif; ?>
689 1117 </div>
690 1118 <div class="right aligned middle aligned column">
691 - <a href="admin.php?page=AddApiKeys" class="ui mini green button"><?php esc_html_e( 'Create New API Key', 'mainwp' ); ?></a>
1119 + <?php if ( static::can_create_rest_api_keys() ) : ?>
1120 + <a href="admin.php?page=AddApiKeys"
1121 + class="ui mini green button"><?php esc_html_e( 'Create New API Key', 'mainwp' ); ?></a>
1122 + <?php endif; ?>
692 1123 </div>
693 1124 </div>
694 1125 </div>
695 1126 </div>
@@ -695,9 +1126,8 @@
695 1126 </div>
696 1127 <?php
697 1128 }
698 1129
699 -
700 1130 /**
701 1131 * Method check_rest_api_updates().
702 1132 *
703 1133 * To Checks for updating REST API keys.
@@ -732,27 +1162,38 @@
732 1162
733 1163
734 1164 /**
735 1165 * Method show_messages().
736 - *
737 - * Show actions messages.
738 1166 */
739 1167 public static function show_messages() {
740 - $msg = '';
741 - if ( isset( $_GET['message'] ) && ( 'saved' === $_GET['message'] || 'created' === $_GET['message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
742 - $msg = esc_html__( 'API Key have been saved successfully!', 'mainwp' );
1168 + $msg = '';
1169 + $tone = 'green';
1170 + if ( isset( $_GET['message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1171 + $message = sanitize_key( wp_unslash( $_GET['message'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
1172 + if ( 'saved' === $message || 'created' === $message ) {
1173 + $msg = esc_html__( 'API Key have been saved successfully!', 'mainwp' );
1174 + } elseif ( 'create_failed' === $message ) {
1175 + $msg = esc_html__( 'Could not save the API key. The credential was generated but the database did not accept the insert, so no key was stored. Please try again or contact MainWP support if the problem persists.', 'mainwp' );
1176 + $tone = 'red';
1177 + }
743 1178 }
744 1179 if ( ! empty( $msg ) ) {
745 1180 ?>
746 - <div class="ui green message"><i class="close icon"></i><?php echo esc_html( $msg ); ?></div>
1181 + <div class="ui <?php echo esc_attr( $tone ); ?> message"><i class="close icon"></i><?php echo esc_html( $msg ); ?></div>
747 1182 <?php
748 1183 }
749 1184 }
750 1185
751 - /** Render REST API SubPage */
1186 + /**
1187 + * Render REST API SubPage
1188 + */
752 1189 public static function render_rest_api_setings() { //phpcs:ignore -- NOSONAR - complex.
753 - if ( ! mainwp_current_user_have_right( 'dashboard', 'manage_dashboard_restapi' ) ) {
754 - mainwp_do_not_have_permissions( esc_html__( 'manage dashboard REST API', 'mainwp' ) );
1190 + // Allow access if user can create or edit REST API keys (manage alone cannot create/edit).
1191 + if ( ! ( static::can_create_rest_api_keys() || static::can_edit_rest_api_keys() ) ) {
1192 + static::render_permission_denied_ui(
1193 + esc_html__( 'API Access', 'mainwp' ),
1194 + esc_html__( 'You do not have permission to manage REST API keys. If you need access to this page please contact the dashboard administrator.', 'mainwp' )
1195 + );
755 1196 return;
756 1197 }
757 1198
758 1199 $edit_key = false;
@@ -785,9 +1226,8 @@
785 1226 }
786 1227 }
787 1228
788 1229 // we need to generate a consumer key and secret and return the result and save it into the database.
789 -
790 1230 $_consumer_key = static::mainwp_generate_rand_hash();
791 1231 $_consumer_secret = static::mainwp_generate_rand_hash();
792 1232
793 1233 $consumer_key = 'ck_' . $_consumer_key;
@@ -795,162 +1235,199 @@
795 1235
796 1236 static::render_header( 'Settings' );
797 1237
798 1238 ?>
799 - <div id="rest-api-settings" class="ui segment">
800 - <h3 class="ui dividing header">
1239 + <div id="rest-api-settings" class="ui padded segment">
1240 + <h2 class="ui dividing header">
801 1241 <?php esc_html_e( 'MainWP REST API Credentials', 'mainwp' ); ?>
802 - <div class="sub header"><?php esc_html_e( 'These API Keys are used for authorization and authentication for the MainWP REST API.', 'mainwp' ); ?></div>
803 - </h3>
804 - <?php if ( MainWP_Utility::show_mainwp_message( 'notice', 'mainwp-rest-api-info-message' ) ) : ?>
805 - <div class="ui info message">
806 - <i class="close icon mainwp-notice-dismiss" notice-id="mainwp-rest-api-info-message"></i>
807 - <?php printf( esc_html__( 'Enable the MainWP REST API functionality and generate API credentials. Check this %1$shelp document%2$s to see all available endpoints.', 'mainwp' ), '<a href="https://mainwp.dev/rest-api/" target="_blank">', '</a>' ); ?>
1242 + <div class="sub header">
1243 + <?php esc_html_e( 'Generate, manage, and revoke API keys to securely connect with the MainWP REST API. Ensure you\'re using the correct version for your integrations.', 'mainwp' ); ?>
808 1244 </div>
809 - <?php endif; ?>
810 - <?php static::show_messages(); ?>
811 - <div id="api-credentials-created" class="ui green message">
812 - <strong><?php esc_html_e( 'API credentials have been successfully generated.', 'mainwp' ); ?></strong><br/>
813 - <?php esc_html_e( 'Please copy the consumer key and secret now as after you leave this page the credentials will no longer be accessible. Use the Description field for easier Key management when needed.', 'mainwp' ); ?>
814 - </div>
815 - <div class="ui info message">
816 - <?php esc_html_e( 'The API Key (Bearer) authentication is only compatible with the MainWP REST API v2', 'mainwp' ); ?>
817 - <?php esc_html_e( 'If you are working with the MainWP REST API v1 (Legacy), click on the "Show Legacy API Credentials" button to obtain your Consumer Key and Consumer Secret.', 'mainwp' ); ?>
818 - </div>
819 - <div class="ui form">
820 - <form method="POST" action="">
821 - <?php wp_nonce_field( 'mainwp-admin-nonce' ); ?>
822 - <input type="hidden" name="wp_nonce" value="<?php echo esc_attr( wp_create_nonce( 'RESTAPI' ) ); ?>" />
823 - <?php
824 - /**
825 - * Action: rest_api_form_top
826 - *
827 - * Fires at the top of REST API form.
828 - *
829 - * @since 4.1
830 - */
831 - do_action( 'rest_api_form_top' );
832 - ?>
833 - <div class="ui grid field">
834 - <label class="six wide column middle aligned"><?php esc_html_e( 'Enable API key', 'mainwp' ); ?></label>
835 - <div class="ten wide column ui toggle checkbox" data-tooltip="<?php esc_attr_e( 'If enabled, the REST API will be activated.', 'mainwp' ); ?>" data-inverted="" data-position="bottom left">
836 - <input type="checkbox" name="mainwp_enable_rest_api" id="mainwp_enable_rest_api" checked="true" aria-label="<?php esc_attr_e( 'Enable REST API key', 'mainwp' ); ?>"/>
1245 + </h2>
1246 + <div id="api-credentials-created" class="ui green message">
1247 + <strong><?php esc_html_e( 'API Key Created Successfully!', 'mainwp' ); ?></strong><br />
1248 + <?php esc_html_e( 'Copy your key and store it securely. It will not be visible again!', 'mainwp' ); ?>
1249 + </div>
1250 +
1251 + <?php static::show_messages(); ?>
1252 +
1253 + <div class="ui info message">
1254 + <?php esc_html_e( 'The API Key (Bearer) authentication is only compatible with the MainWP REST API v2', 'mainwp' ); ?>
1255 + <?php esc_html_e( 'If you are working with the MainWP REST API v1 (Legacy), click on the "Show Legacy API Credentials" button to obtain your Consumer Key and Consumer Secret.', 'mainwp' ); ?>
1256 + </div>
1257 +
1258 + <div class="ui form">
1259 + <form method="POST" action="">
1260 + <?php MainWP_UI::generate_wp_nonce( 'mainwp-admin-nonce' ); ?>
1261 + <input type="hidden" name="wp_nonce" value="<?php echo esc_attr( wp_create_nonce( 'RESTAPI' ) ); ?>" />
1262 + <?php
1263 + /**
1264 + * Action: rest_api_form_top
1265 + *
1266 + * Fires at the top of REST API form.
1267 + *
1268 + * @since 4.1
1269 + */
1270 + do_action( 'rest_api_form_top' );
1271 + ?>
1272 + <div class="ui grid field">
1273 + <label class="six wide column middle aligned" for="mainwp_enable_rest_api"><?php esc_html_e( 'Enable API key', 'mainwp' ); ?></label>
1274 + <div class="ten wide column ui toggle checkbox">
1275 + <input type="checkbox" name="mainwp_enable_rest_api" id="mainwp_enable_rest_api" checked="true"
1276 + aria-label="<?php esc_attr_e( 'Enable REST API key', 'mainwp' ); // NOSONAR. ?>" />
1277 + </div>
1278 + </div>
1279 + <div class="ui grid field">
1280 + <label class="six wide column middle aligned" for="mainwp_rest_add_api_key_desc"><?php esc_html_e( 'API key name', 'mainwp' ); ?></label>
1281 + <div class="five wide column">
1282 + <input type="text" name="mainwp_rest_add_api_key_desc" id="mainwp_rest_add_api_key_desc" value=""
1283 + aria-label="<?php esc_attr_e( 'API key name.', 'mainwp' ); ?>" />
1284 + </div>
1285 + </div>
1286 +
1287 + <div class="ui grid field">
1288 + <label
1289 + class="six wide column middle aligned" for="mainwp_rest_api_keys_compatible_v1"><?php esc_html_e( 'Enable MainWP REST API v1 Compatibility', 'mainwp' ); ?></label>
1290 + <div class="ten wide column ui toggle checkbox"
1291 + data-tooltip="<?php esc_attr_e( 'If enabled, the credentials will be compatible with REST API v1.', 'mainwp' ); ?>"
1292 + data-inverted="" data-position="bottom left">
1293 + <input type="checkbox" name="mainwp_rest_api_keys_compatible_v1"
1294 + id="mainwp_rest_api_keys_compatible_v1"
1295 + aria-label="<?php esc_attr_e( 'If enabled, the credentials will be compatible with REST API v1.', 'mainwp' ); ?>" />
1296 + </div>
1297 + </div>
1298 +
1299 + <?php $init_pers = 'r,w,d'; ?>
1300 +
1301 + <div class="ui grid field">
1302 + <label class="six wide column middle aligned"><?php esc_html_e( 'Permissions', 'mainwp' ); ?></label>
1303 + <div class="ten wide column">
1304 + <div class="mainwp-rest-api-permission-chips" data-init-value="<?php echo esc_attr( $init_pers ); ?>" data-api-version="v2">
1305 + <input type="hidden" name="mainwp_rest_api_key_edit_pers" value="" />
1306 + <button type="button" class="ui basic label mainwp-permission-chip" data-permission="r">
1307 + <i class="book icon"></i>
1308 + <?php esc_html_e( 'Read', 'mainwp' ); ?>
1309 + </button>
1310 + <button type="button" class="ui basic label mainwp-permission-chip mainwp-v2-chip" data-permission="w,d">
1311 + <i class="pencil icon"></i>
1312 + <?php esc_html_e( 'Write & Delete', 'mainwp' ); ?>
1313 + </button>
1314 + <button type="button" class="ui basic label mainwp-permission-chip mainwp-v1-chip" data-permission="w" style="display:none;">
1315 + <i class="pencil icon"></i>
1316 + <?php esc_html_e( 'Write', 'mainwp' ); ?>
1317 + </button>
1318 + <button type="button" class="ui basic label mainwp-permission-chip mainwp-v1-chip" data-permission="d" style="display:none;">
1319 + <i class="trash icon"></i>
1320 + <?php esc_html_e( 'Delete', 'mainwp' ); ?>
1321 + </button>
837 1322 </div>
1323 + <div class="mainwp-permission-chips-help">
1324 + <span class="ui small grey text"><?php esc_html_e( 'Choose what actions this key can perform on your Dashboard data.', 'mainwp' ); ?></span>
1325 + </div>
838 1326 </div>
839 - <div class="ui grid field">
840 - <label class="six wide column middle aligned"><?php esc_html_e( 'API key name', 'mainwp' ); ?></label>
841 - <div class="five wide column" data-tooltip="<?php esc_attr_e( 'Name your API Key.', 'mainwp' ); ?>" data-inverted="" data-position="bottom left">
842 - <input type="text" name="mainwp_rest_add_api_key_desc" id="mainwp_rest_add_api_key_desc" value="" aria-label="<?php esc_attr_e( 'API key name.', 'mainwp' ); ?>"/>
1327 + </div>
1328 +
1329 + <?php
1330 + $token = $_consumer_secret . '==' . $_consumer_key;
1331 + ?>
1332 + <div class="ui grid field">
1333 + <label
1334 + class="six wide column middle aligned" for="mainwp_rest_token"><?php esc_html_e( 'API key (Bearer token)', 'mainwp' ); ?></label>
1335 + <div class="five wide column">
1336 + <input type="text" name="mainwp_rest_token" id="mainwp_rest_token"
1337 + value="<?php echo esc_html( $token ); ?>" readonly
1338 + aria-label="<?php esc_attr_e( 'API Token', 'mainwp' ); ?>" />
1339 + </div>
1340 + <div class="five wide column">
1341 + <input id="mainwp_rest_token_clipboard_button" style="display:nonce;" type="button" name=""
1342 + class="ui green basic button copy-to-clipboard"
1343 + value="<?php esc_attr_e( 'Copy to Clipboard', 'mainwp' ); // NOSONAR. ?>">
1344 + </div>
1345 + </div>
1346 +
1347 + <div id="mainwp-legacy-api-credentials" class="ui segment" style="display:none">
1348 + <h3 class="ui dividing header">
1349 + <?php esc_html_e( 'Legacy API Credentials', 'mainwp' ); ?>
1350 + <div class="sub header">
1351 + <?php esc_html_e( 'Authentication compatible only with the MainWP REST API version 1!', 'mainwp' ); ?>
843 1352 </div>
844 - </div>
1353 + </h3>
845 1354
846 - <?php
847 - $token = $_consumer_secret . '==' . $_consumer_key;
848 - ?>
849 1355 <div class="ui grid field">
850 - <label class="six wide column middle aligned"><?php esc_html_e( 'API key (Bearer token)', 'mainwp' ); ?></label>
1356 + <label
1357 + class="six wide column middle aligned" for="mainwp_consumer_key"><?php esc_html_e( 'Consumer key', 'mainwp' ); ?></label>
851 1358 <div class="five wide column">
852 - <input type="text" name="mainwp_rest_token" id="mainwp_rest_token" value="<?php echo esc_html( $token ); ?>" readonly aria-label="<?php esc_attr_e( 'API Token', 'mainwp' ); ?>"/>
1359 + <input type="text" name="mainwp_consumer_key" id="mainwp_consumer_key"
1360 + value="<?php echo esc_html( $consumer_key ); ?>" readonly
1361 + aria-label="<?php esc_attr_e( 'Consumer key', 'mainwp' ); ?>" />
853 1362 </div>
854 1363 <div class="five wide column">
855 - <input id="mainwp_rest_token_clipboard_button" style="display:nonce;" type="button" name="" class="ui green basic button copy-to-clipboard" value="<?php esc_attr_e( 'Copy to Clipboard', 'mainwp' ); ?>">
1364 + <input id="mainwp_consumer_key_clipboard_button" style="display:nonce;" type="button" name=""
1365 + class="ui green basic button copy-to-clipboard"
1366 + value="<?php esc_attr_e( 'Copy to Clipboard', 'mainwp' ); ?>">
856 1367 </div>
857 1368 </div>
858 1369
859 - <?php $init_pers = 'r,w,d'; ?>
860 1370 <div class="ui grid field">
861 - <label class="six wide column middle aligned"><?php esc_html_e( 'Permissions', 'mainwp' ); ?></label>
1371 + <label
1372 + class="six wide column middle aligned" for="mainwp_consumer_secret"><?php esc_html_e( 'Consumer secret', 'mainwp' ); ?></label>
862 1373 <div class="five wide column">
863 - <div class="ui multiple selection dropdown" init-value="<?php echo esc_attr( $init_pers ); ?>">
864 - <input name="mainwp_rest_api_key_edit_pers" value="" type="hidden">
865 - <i class="dropdown icon"></i>
866 - <div class="default text"><?php echo ( '' === $init_pers ) ? esc_html__( 'No Permissions selected.', 'mainwp' ) : ''; ?></div>
867 - <div class="menu">
868 - <div class="item" data-value="r"><?php esc_html_e( 'Read', 'mainwp' ); ?></div>
869 - <div class="item" data-value="w"><?php esc_html_e( 'Write', 'mainwp' ); ?></div>
870 - <div class="item" data-value="d"><?php esc_html_e( 'Delete', 'mainwp' ); ?></div>
871 - </div>
872 - </div>
1374 + <input type="text" name="mainwp_consumer_secret" id="mainwp_consumer_secret"
1375 + value="<?php echo esc_html( $consumer_secret ); ?>" readonly
1376 + aria-label="<?php esc_attr_e( 'Consumer secret', 'mainwp' ); ?>" />
873 1377 </div>
874 - </div>
875 -
876 - <div class="ui grid field">
877 - <label class="six wide column middle aligned"></label>
878 1378 <div class="five wide column">
879 - <a href="#" id="mainwp-show-legacy-api-credentials" class="ui mini basic button"><?php esc_html_e( 'Show Legacy API Credentials', 'mainwp' ); ?></a>
1379 + <input id="mainwp_consumer_secret_clipboard_button" style="display:nonce;" type="button" name=""
1380 + class="ui green basic button copy-to-clipboard"
1381 + value="<?php esc_attr_e( 'Copy to Clipboard', 'mainwp' ); ?>">
880 1382 </div>
881 1383 </div>
882 -
883 - <div id="mainwp-legacy-api-credentials" class="ui segment" style="display:none">
884 - <h3 class="ui dividing header">
885 - <?php esc_html_e( 'Legacy API Credentials', 'mainwp' ); ?>
886 - <div class="sub header"><?php esc_html_e( 'Authentication compatible only with the MainWP REST API version 1!', 'mainwp' ); ?></div>
887 - </h3>
888 -
889 - <div class="ui grid field">
890 - <label class="six wide column middle aligned"><?php esc_html_e( 'Consumer key', 'mainwp' ); ?></label>
891 - <div class="five wide column">
892 - <input type="text" name="mainwp_consumer_key" id="mainwp_consumer_key" value="<?php echo esc_html( $consumer_key ); ?>" readonly aria-label="<?php esc_attr_e( 'Consumer key', 'mainwp' ); ?>"/>
893 - </div>
894 - <div class="five wide column">
895 - <input id="mainwp_consumer_key_clipboard_button" style="display:nonce;" type="button" name="" class="ui green basic button copy-to-clipboard" value="<?php esc_attr_e( 'Copy to Clipboard', 'mainwp' ); ?>">
896 - </div>
897 - </div>
898 -
899 - <div class="ui grid field">
900 - <label class="six wide column middle aligned"><?php esc_html_e( 'Consumer secret', 'mainwp' ); ?></label>
901 - <div class="five wide column">
902 - <input type="text" name="mainwp_consumer_secret" id="mainwp_consumer_secret" value="<?php echo esc_html( $consumer_secret ); ?>" readonly aria-label="<?php esc_attr_e( 'Consumer secret', 'mainwp' ); ?>"/>
903 - </div>
904 - <div class="five wide column">
905 - <input id="mainwp_consumer_secret_clipboard_button" style="display:nonce;" type="button" name="" class="ui green basic button copy-to-clipboard" value="<?php esc_attr_e( 'Copy to Clipboard', 'mainwp' ); ?>">
906 - </div>
907 - </div>
908 -
909 - <div class="ui grid field">
910 - <label class="six wide column middle aligned"><?php esc_html_e( 'Enable MainWP REST API v1 Compatibility', 'mainwp' ); ?></label>
911 - <div class="ten wide column ui toggle checkbox" data-tooltip="<?php esc_attr_e( 'If enabled, the credentials will be compatible with REST API v1.', 'mainwp' ); ?>" data-inverted="" data-position="bottom left">
912 - <input type="checkbox" name="mainwp_rest_api_keys_compatible_v1" id="mainwp_rest_api_keys_compatible_v1" aria-label="<?php esc_attr_e( 'If enabled, the credentials will be compatible with REST API v1.', 'mainwp' ); ?>"/>
913 - </div>
914 - </div>
915 1384 </div>
916 - <?php
917 - /**
918 - * Action: rest_api_form_bottom
919 - *
920 - * Fires at the bottom of REST API form.
921 - *
922 - * @since 4.1
923 - */
924 - do_action( 'rest_api_form_bottom' );
925 - ?>
926 - <div class="ui divider"></div>
927 - <input type="submit" name="submit" id="submit-save-settings-button" class="ui green big button" value="<?php esc_attr_e( 'Save Settings', 'mainwp' ); ?>"/>
928 - <div style="clear:both"></div>
929 - </form>
930 - </div>
1385 + <?php
1386 + /**
1387 + * Action: rest_api_form_bottom
1388 + *
1389 + * Fires at the bottom of REST API form.
1390 + *
1391 + * @since 4.1
1392 + */
1393 + do_action( 'rest_api_form_bottom' );
1394 + ?>
1395 + <div class="ui divider"></div>
1396 + <input type="submit" name="submit" id="submit-save-settings-button" class="ui green big button"
1397 + value="<?php esc_attr_e( 'Save Key', 'mainwp' ); ?>" />
1398 + <div style="clear:both"></div>
1399 + </form>
931 1400 </div>
932 - <script type="text/javascript">
933 - jQuery(function($) {
934 - //we are going to inject the values into the copy buttons to make things easier for people
935 - $('#mainwp_consumer_key_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $consumer_key ); ?>');
936 - $('#mainwp_consumer_secret_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $consumer_secret ); ?>');
937 - $('#mainwp_rest_token_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $token ); ?>'); //phpcs:ignore -- NOSONAR - token value ok.
938 - //initiate clipboard
939 - new ClipboardJS('.copy-to-clipboard');
940 - //show copy to clipboard buttons
941 - $('.copy-to-clipboard').show();
1401 + </div>
1402 + <div id="mainwp-api-key-copied-confirm-modal" class="ui mini modal">
1403 + <i class="close icon"></i>
1404 + <div class="header">
1405 + <?php esc_html_e( 'API Key Copied', 'mainwp' ); ?>
1406 + </div>
1407 + <div class="content">
1408 + <p><?php esc_html_e( 'Your API key has been copied to your clipboard.', 'mainwp' ); ?></p>
1409 + </div>
1410 + </div>
1411 + <script type="text/javascript">
1412 + jQuery(function ($) {
1413 + //we are going to inject the values into the copy buttons to make things easier for people
1414 + $('#mainwp_consumer_key_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $consumer_key ); ?>');
1415 + $('#mainwp_consumer_secret_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $consumer_secret ); ?>');
1416 + $('#mainwp_rest_token_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $token ); ?>'); //phpcs:ignore -- NOSONAR - token value ok.
1417 + //initiate clipboard
1418 + new ClipboardJS('.copy-to-clipboard');
1419 + //show copy to clipboard buttons
1420 + $('.copy-to-clipboard').show();
942 1421
943 - $('#mainwp-show-legacy-api-credentials').on('click', function() {
944 - $('#mainwp-legacy-api-credentials').toggle();
945 - });
1422 + $('#mainwp_rest_api_keys_compatible_v1').on('change', function () {
1423 + $('#mainwp-legacy-api-credentials').toggle();
946 1424 });
947 -
948 -
949 - </script>
1425 + });
1426 + </script>
950 1427 <?php
951 1428
952 - static::render_footer( 'Settings' );
1429 + static::render_footer();
953 1430 }
954 1431
955 1432 /**
956 1433 * Method mainwp_generate_rand_hash()
@@ -971,9 +1448,9 @@
971 1448 * Method render_rest_api_v2_edit().
972 1449 *
973 1450 * Render REST API edit screen.
974 1451 *
975 - * @param array $item The Key edit.
1452 + * @param mixed $item The Key edit.
976 1453 */
977 1454 public static function render_rest_api_v2_edit( $item ) { //phpcs:ignore -- NOSONAR - complex.
978 1455 $keyid = $item->key_id;
979 1456 $edit_desc = $item->description;
@@ -980,40 +1457,48 @@
980 1457 $enabled = $item->enabled ? true : false;
981 1458 $ending = $item->truncated_key;
982 1459 $perms = $item->permissions;
983 1460
1461 + $init_pers = '';
984 1462 if ( 'read' === $perms ) {
985 1463 $init_pers = 'r';
1464 + } elseif ( 'write' === $perms || 'delete' === $perms ) {
1465 + $init_pers = 'w,d';
1466 + } elseif ( 'read_write' === $perms ) {
1467 + $init_pers = 'r,w,d';
986 1468 }
987 - if ( 'write' === $perms ) {
988 - $init_pers = 'w';
989 - }
990 - if ( 'read_write' === $perms ) {
991 - $init_pers = 'r,w';
992 - }
993 1469 static::render_header( 'Edit' );
1470 + $el_id_res_api_1 = 'rest-api-settings';
994 1471 ?>
995 - <div id="rest-api-settings" class="ui segment">
1472 + <div id="<?php echo esc_attr( $el_id_res_api_1 ); ?>" class="ui segment">
996 1473 <div class="ui form">
997 - <form method="POST" action="">
998 - <?php wp_nonce_field( 'mainwp-admin-nonce' ); ?>
999 - <input type="hidden" name="edit_key_nonce" value="<?php echo esc_attr( wp_create_nonce( 'edit-key-nonce-' . $keyid ) ); ?>" />
1000 - <input type="hidden" name="editkey_id" value="<?php echo esc_html( $keyid ); ?>" />
1001 - <input type="hidden" name="rest_v2_edit" value="1" />
1002 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1003 - <label class="six wide column middle aligned">
1474 + <form method="POST" action="">
1475 + <?php MainWP_UI::generate_wp_nonce( 'mainwp-admin-nonce' ); ?>
1476 + <input type="hidden" name="edit_key_nonce"
1477 + value="<?php echo esc_attr( wp_create_nonce( 'edit-key-nonce-' . $keyid ) ); ?>" />
1478 + <input type="hidden" name="editkey_id" value="<?php echo esc_html( $keyid ); ?>" />
1479 + <input type="hidden" name="rest_v2_edit" value="1" />
1480 + <div
1481 + class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1482 + <label class="six wide column middle aligned" for="mainwp_enable_rest_api">
1004 1483 <?php
1005 1484 if ( $item ) {
1006 1485 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $enabled );
1007 1486 }
1487 + $el_id_en_res_api_1 = 'mainwp_enable_rest_api';
1008 1488 ?>
1009 1489 <?php esc_html_e( 'Enable REST API Key', 'mainwp' ); ?></label>
1010 - <div class="ten wide column ui toggle checkbox" data-tooltip="<?php esc_attr_e( 'If enabled, the REST API will be activated.', 'mainwp' ); ?>" data-inverted="" data-position="bottom left">
1011 - <input type="checkbox" class="settings-field-value-change-handler" name="mainwp_enable_rest_api" id="mainwp_enable_rest_api" value="1" <?php echo $enabled ? 'checked="true"' : ''; ?> aria-label="<?php esc_attr_e( 'Enable REST API key', 'mainwp' ); ?>" />
1012 - </div>
1490 + <div class="ten wide column ui toggle checkbox"
1491 + data-tooltip="<?php esc_attr_e( 'If enabled, the REST API will be activated.', 'mainwp' ); ?>"
1492 + data-inverted="" data-position="bottom left">
1493 + <input type="checkbox" class="settings-field-value-change-handler" name="mainwp_enable_rest_api"
1494 + id="<?php echo esc_attr( $el_id_en_res_api_1 ); ?>" value="1" <?php echo $enabled ? 'checked="true"' : ''; ?>
1495 + aria-label="<?php esc_attr_e( 'Enable REST API key', 'mainwp' ); ?>" />
1013 1496 </div>
1014 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"">
1015 - <label class="six wide column middle aligned">
1497 + </div>
1498 + <div
1499 + class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1500 + <label class="six wide column middle aligned" for="mainwp_rest_api_key_desc">
1016 1501 <?php
1017 1502 if ( $item ) {
1018 1503 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
1019 1504 }
@@ -1018,14 +1503,17 @@
1018 1503 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
1019 1504 }
1020 1505 ?>
1021 1506 <?php esc_html_e( 'Description', 'mainwp' ); ?></label>
1022 - <div class="five wide column">
1023 - <input type="text" class="settings-field-value-change-handler" name="mainwp_rest_api_key_desc" id="mainwp_rest_api_key_desc" value="<?php echo esc_html( $edit_desc ); ?>" aria-label="<?php esc_attr_e( 'Key description', 'mainwp' ); ?>"/>
1024 - </div>
1507 + <div class="five wide column">
1508 + <input type="text" class="settings-field-value-change-handler" name="mainwp_rest_api_key_desc"
1509 + id="mainwp_rest_api_key_desc" value="<?php echo esc_html( $edit_desc ); ?>"
1510 + aria-label="<?php esc_attr_e( 'Key description', 'mainwp' ); ?>" />
1025 1511 </div>
1026 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"">
1027 - <label class="six wide column middle aligned">
1512 + </div>
1513 + <div
1514 + class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1515 + <label class="six wide column middle aligned" for="none_preset_value">
1028 1516 <?php
1029 1517 if ( $item ) {
1030 1518 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
1031 1519 }
@@ -1030,16 +1518,19 @@
1030 1518 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
1031 1519 }
1032 1520 ?>
1033 1521 <?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></label>
1034 - <div class="five wide column">
1035 - <div class="ui disabled input">
1036 - <input type="text" class="settings-field-value-change-handler" value="<?php echo esc_attr( '...' . $ending ); ?>" aria-label="<?php esc_attr_e( 'Consumer key ending in...', 'mainwp' ); ?>"/>
1037 - </div>
1522 + <div class="five wide column">
1523 + <div class="ui disabled input">
1524 + <input type="text" class="settings-field-value-change-handler"
1525 + value="<?php echo esc_attr( '...' . $ending ); ?>"
1526 + aria-label="<?php esc_attr_e( 'Consumer key ending in...', 'mainwp' ); ?>" />
1038 1527 </div>
1039 1528 </div>
1040 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>" default-indi-value="3">
1041 - <label class="six wide column middle aligned">
1529 + </div>
1530 + <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"
1531 + default-indi-value="3">
1532 + <label class="six wide column middle aligned">
1042 1533 <?php
1043 1534 if ( $item ) {
1044 1535 $tmp = explode( ',', $init_pers );
1045 1536 $visible = 2 !== count( $tmp );
@@ -1046,32 +1537,37 @@
1046 1537 MainWP_Settings_Indicator::render_indicator( '', '', $visible );
1047 1538 }
1048 1539 ?>
1049 1540 <?php esc_html_e( 'Permissions', 'mainwp' ); ?></label>
1050 - <div class="five wide column">
1051 - <div class="ui multiple selection dropdown" init-value="<?php echo esc_attr( $init_pers ); ?>">
1052 - <input name="mainwp_rest_api_key_edit_pers" class="settings-field-value-change-handler" value="" type="hidden">
1053 - <i class="dropdown icon"></i>
1054 - <div class="default text"><?php echo ( '' === $init_pers ) ? esc_html__( 'No Permissions selected.', 'mainwp' ) : ''; ?></div>
1055 - <div class="menu">
1056 - <div class="item" data-value="r"><?php esc_html_e( 'Read', 'mainwp' ); ?></div>
1057 - <div class="item" data-value="w"><?php esc_html_e( 'Write', 'mainwp' ); ?></div>
1058 - </div>
1059 - </div>
1541 + <div class="ten wide column">
1542 + <div class="mainwp-rest-api-permission-chips" data-init-value="<?php echo esc_attr( $init_pers ); ?>" data-api-version="v2">
1543 + <input type="hidden" name="mainwp_rest_api_key_edit_pers" class="settings-field-value-change-handler" value="" />
1544 + <button type="button" class="ui button chip mainwp-permission-chip" data-permission="r">
1545 + <i class="book icon"></i>
1546 + <?php esc_html_e( 'Read', 'mainwp' ); ?>
1547 + </button>
1548 + <button type="button" class="ui button chip mainwp-permission-chip" data-permission="w,d">
1549 + <i class="pencil icon"></i>
1550 + <?php esc_html_e( 'Write & Delete', 'mainwp' ); ?>
1551 + </button>
1060 1552 </div>
1553 + <div class="mainwp-permission-chips-help" style="margin-top: 8px; font-size: 12px; color: #666;">
1554 + <?php esc_html_e( 'Choose what actions this key can perform on your Dashboard data.', 'mainwp' ); ?>
1555 + </div>
1061 1556 </div>
1062 - <div class="ui divider"></div>
1063 - <input type="submit" name="submit" id="submit" class="ui green big button" value="<?php esc_attr_e( 'Save API Key', 'mainwp' ); ?>"/>
1064 - <div style="clear:both"></div>
1065 - </form>
1066 - </div>
1557 + </div>
1558 + <div class="ui divider"></div>
1559 + <input type="submit" name="submit" id="submit" class="ui green big button"
1560 + value="<?php esc_attr_e( 'Save API Key', 'mainwp' ); ?>" />
1561 + <div style="clear:both"></div>
1562 + </form>
1563 + </div>
1067 1564
1068 1565 </div>
1069 1566 <?php
1070 - static::render_footer( 'Edit' );
1567 + static::render_footer();
1071 1568 }
1072 1569
1073 -
1074 1570 /**
1075 1571 * Method render_rest_api_edit().
1076 1572 *
1077 1573 * Render REST API edit screen.
@@ -1092,29 +1588,38 @@
1092 1588 $init_pers = 'r,w,d'; // to compatible.
1093 1589 }
1094 1590
1095 1591 static::render_header( 'Edit' );
1592 + $el_id_res_api_2 = 'rest-api-settings';
1096 1593 ?>
1097 - <div id="rest-api-settings" class="ui segment">
1594 + <div id="<?php echo esc_attr( $el_id_res_api_2 ); ?>" class="ui segment">
1098 1595 <div class="ui form">
1099 - <form method="POST" action="">
1100 - <?php wp_nonce_field( 'mainwp-admin-nonce' ); ?>
1101 - <input type="hidden" name="edit_key_nonce" value="<?php echo esc_attr( wp_create_nonce( 'edit-key-nonce-' . $keyid ) ); ?>" />
1102 - <input type="hidden" name="editkey_id" value="<?php echo esc_html( $keyid ); ?>" />
1103 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1104 - <label class="six wide column middle aligned">
1596 + <form method="POST" action="">
1597 + <?php MainWP_UI::generate_wp_nonce( 'mainwp-admin-nonce' ); ?>
1598 + <input type="hidden" name="edit_key_nonce"
1599 + value="<?php echo esc_attr( wp_create_nonce( 'edit-key-nonce-' . $keyid ) ); ?>" />
1600 + <input type="hidden" name="editkey_id" value="<?php echo esc_html( $keyid ); ?>" />
1601 + <div
1602 + class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1603 + <label class="six wide column middle aligned" for="mainwp_enable_rest_api">
1105 1604 <?php
1106 1605 if ( $item ) {
1107 1606 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $enabled );
1108 1607 }
1608 + $el_id_en_res_api_2 = 'mainwp_enable_rest_api';
1109 1609 ?>
1110 1610 <?php esc_html_e( 'Enable API key', 'mainwp' ); ?></label>
1111 - <div class="ten wide column ui toggle checkbox" data-tooltip="<?php esc_attr_e( 'If enabled, the REST API will be activated.', 'mainwp' ); ?>" data-inverted="" data-position="bottom left">
1112 - <input type="checkbox" class="settings-field-value-change-handler" name="mainwp_enable_rest_api" id="mainwp_enable_rest_api" value="1" <?php echo $enabled ? 'checked="true"' : ''; ?> aria-label="<?php esc_attr_e( 'Enable REST API key', 'mainwp' ); ?>" />
1113 - </div>
1611 + <div class="ten wide column ui toggle checkbox"
1612 + data-tooltip="<?php esc_attr_e( 'If enabled, the REST API will be activated.', 'mainwp' ); ?>"
1613 + data-inverted="" data-position="bottom left">
1614 + <input type="checkbox" class="settings-field-value-change-handler" name="mainwp_enable_rest_api"
1615 + id="<?php echo esc_attr( $el_id_en_res_api_2 ); ?>" value="1" <?php echo $enabled ? 'checked="true"' : ''; ?>
1616 + aria-label="<?php esc_attr_e( 'Enable REST API key', 'mainwp' ); ?>" />
1114 1617 </div>
1115 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"">
1116 - <label class="six wide column middle aligned">
1618 + </div>
1619 + <div
1620 + class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1621 + <label class="six wide column middle aligned" for="none_preset_value">
1117 1622 <?php
1118 1623 if ( $item ) {
1119 1624 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
1120 1625 }
@@ -1119,14 +1624,17 @@
1119 1624 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
1120 1625 }
1121 1626 ?>
1122 1627 <?php esc_html_e( 'Description', 'mainwp' ); ?></label>
1123 - <div class="five wide column">
1124 - <input type="text" class="settings-field-value-change-handler" name="mainwp_rest_api_key_desc" id="mainwp_rest_api_key_desc" value="<?php echo esc_html( $edit_desc ); ?>" aria-label="<?php esc_attr_e( 'Key description', 'mainwp' ); ?>"/>
1125 - </div>
1628 + <div class="five wide column">
1629 + <input type="text" class="settings-field-value-change-handler" name="mainwp_rest_api_key_desc"
1630 + id="mainwp_rest_api_key_desc" value="<?php echo esc_html( $edit_desc ); ?>"
1631 + aria-label="<?php esc_attr_e( 'Key description', 'mainwp' ); ?>" />
1126 1632 </div>
1127 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"">
1128 - <label class="six wide column middle aligned">
1633 + </div>
1634 + <div
1635 + class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
1636 + <label class="six wide column middle aligned" for="none_preset_value">
1129 1637 <?php
1130 1638 if ( $item ) {
1131 1639 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
1132 1640 }
@@ -1131,16 +1639,19 @@
1131 1639 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
1132 1640 }
1133 1641 ?>
1134 1642 <?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></label>
1135 - <div class="five wide column">
1136 - <div class="ui disabled input">
1137 - <input type="text" class="settings-field-value-change-handler" value="<?php echo esc_attr( '...' . $ending ); ?>" aria-label="<?php esc_attr_e( 'Consumer key ending in...', 'mainwp' ); ?>"/>
1138 - </div>
1643 + <div class="five wide column">
1644 + <div class="ui disabled input">
1645 + <input type="text" class="settings-field-value-change-handler"
1646 + value="<?php echo esc_attr( '...' . $ending ); ?>"
1647 + aria-label="<?php esc_attr_e( 'Consumer key ending in...', 'mainwp' ); ?>" />
1139 1648 </div>
1140 1649 </div>
1141 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>" default-indi-value="3">
1142 - <label class="six wide column middle aligned">
1650 + </div>
1651 + <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"
1652 + default-indi-value="3">
1653 + <label class="six wide column middle aligned">
1143 1654 <?php
1144 1655 if ( $item ) {
1145 1656 $tmp = explode( ',', $init_pers );
1146 1657 $visible = 3 !== count( $tmp );
@@ -1147,30 +1658,40 @@
1147 1658 MainWP_Settings_Indicator::render_indicator( '', '', $visible );
1148 1659 }
1149 1660 ?>
1150 1661 <?php esc_html_e( 'Permissions', 'mainwp' ); ?></label>
1151 - <div class="five wide column">
1152 - <div class="ui multiple selection dropdown" init-value="<?php echo esc_attr( $init_pers ); ?>">
1153 - <input name="mainwp_rest_api_key_edit_pers" class="settings-field-value-change-handler" value="" type="hidden">
1154 - <i class="dropdown icon"></i>
1155 - <div class="default text"><?php echo ( '' === $init_pers ) ? esc_html__( 'No Permissions selected.', 'mainwp' ) : ''; ?></div>
1156 - <div class="menu">
1157 - <div class="item" data-value="r"><?php esc_html_e( 'Read', 'mainwp' ); ?></div>
1158 - <div class="item" data-value="w"><?php esc_html_e( 'Write', 'mainwp' ); ?></div>
1159 - <div class="item" data-value="d"><?php esc_html_e( 'Delete', 'mainwp' ); ?></div>
1160 - </div>
1161 - </div>
1662 + <div class="ten wide column">
1663 + <div class="mainwp-rest-api-permission-chips" data-init-value="<?php echo esc_attr( $init_pers ); ?>" data-api-version="v1">
1664 + <input type="hidden" name="mainwp_rest_api_key_edit_pers" class="settings-field-value-change-handler" value="" />
1665 + <button type="button" class="ui button chip mainwp-permission-chip" data-permission="r">
1666 + <i class="book icon"></i>
1667 + <?php esc_html_e( 'Read', 'mainwp' ); ?>
1668 + </button>
1669 + <button type="button" class="ui button chip mainwp-permission-chip" data-permission="w">
1670 + <i class="pencil icon"></i>
1671 + <?php esc_html_e( 'Write', 'mainwp' ); ?>
1672 + </button>
1673 + <button type="button" class="ui button chip mainwp-permission-chip" data-permission="d">
1674 + <i class="trash icon"></i>
1675 + <?php esc_html_e( 'Delete', 'mainwp' ); ?>
1676 + </button>
1162 1677 </div>
1678 + <div class="mainwp-permission-chips-help" style="margin-top: 8px; font-size: 12px; color: #666;">
1679 + <?php esc_html_e( 'Choose what actions this key can perform on your Dashboard data.', 'mainwp' ); ?>
1680 + </div>
1163 1681 </div>
1164 - <div class="ui divider"></div>
1165 - <input type="submit" name="submit" id="submit" class="ui green big button" value="<?php esc_attr_e( 'Save API Key', 'mainwp' ); ?>"/>
1166 - <div style="clear:both"></div>
1167 - </form>
1168 - </div>
1682 + </div>
1683 + <div class="ui divider"></div>
1684 + <?php $el_id_sbm_btn_2 = 'submit'; ?>
1685 + <input type="submit" name="submit" id="<?php echo esc_attr( $el_id_sbm_btn_2 ); ?>"
1686 + class="ui green big button" value="<?php esc_attr_e( 'Save API Key', 'mainwp' ); ?>" />
1687 + <div style="clear:both"></div>
1688 + </form>
1689 + </div>
1169 1690
1170 1691 </div>
1171 1692 <?php
1172 - static::render_footer( 'Edit' );
1693 + static::render_footer();
1173 1694 }
1174 1695
1175 1696 /**
1176 1697 * Method check_rest_api_enabled().
@@ -1196,13 +1717,14 @@
1196 1717 }
1197 1718 }
1198 1719
1199 1720 $args = array(
1200 - 'method' => 'GET',
1201 - 'timeout' => 45,
1202 - 'headers' => array(
1721 + 'method' => 'GET',
1722 + 'timeout' => 45,
1723 + 'headers' => array(
1203 1724 'content-type' => 'application/json',
1204 1725 ),
1726 + 'sslverify' => (bool) get_option( 'mainwp_sslVerifyCertificate', true ),
1205 1727 );
1206 1728
1207 1729 if ( $check_logged_in && ! empty( $cookies ) ) {
1208 1730 $args['cookies'] = $cookies;
@@ -1207,9 +1729,9 @@
1207 1729 if ( $check_logged_in && ! empty( $cookies ) ) {
1208 1730 $args['cookies'] = $cookies;
1209 1731 }
1210 1732
1211 - $site_url = get_option( 'siteurl' );
1733 + $site_url = get_option( 'home' );
1212 1734 $response = wp_remote_post( $site_url . '/wp-json', $args );
1213 1735 $body = wp_remote_retrieve_body( $response );
1214 1736 $data = is_string( $body ) ? json_decode( $body, true ) : false;
1215 1737
@@ -1229,15 +1751,26 @@
1229 1751 public static function mainwp_help_content() {
1230 1752 $allow_pages = array( 'RESTAPI', 'AddApiKeys' );
1231 1753 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1232 1754 ?>
1233 - <p><?php esc_html_e( 'If you need help with the MainWP REST API, please review following help documents', 'mainwp' ); ?></p>
1755 + <p><?php esc_html_e( 'If you need help with the MainWP REST API, please review following help documents', 'mainwp' ); ?>
1756 + </p>
1234 1757 <div class="ui list">
1235 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-rest-api/" target="_blank">REST API</a></div> <?php // NOSONAR -- compatible with help. ?>
1236 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-rest-api/#how-to-enable-rest-api" target="_blank">Enable REST API</a></div> <?php // NOSONAR -- compatible with help. ?>
1237 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-rest-api/#rest-api-permissions" target="_blank">REST API Permissions</a></div> <?php // NOSONAR -- compatible with help. ?>
1238 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-rest-api/#how-to-disable-rest-api" target="_blank">Disable REST API</a></div> <?php // NOSONAR -- compatible with help. ?>
1239 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-rest-api/#how-to-delete-rest-api-keys" target="_blank">Delete REST API Key</a></div> <?php // NOSONAR -- compatible with help. ?>
1758 + <div class="item"><i class="external alternate icon"></i> <a
1759 + href="https://docs.mainwp.com/dashboard/overview/mainwp-rest-api" target="_blank">REST API</a></div>
1760 + <?php // NOSONAR -- compatible with help. ?>
1761 + <div class="item"><i class="external alternate icon"></i> <a
1762 + href="https://docs.mainwp.com/dashboard/overview/mainwp-rest-api#enable-rest-api" target="_blank">Enable
1763 + REST API</a></div> <?php // NOSONAR -- compatible with help. ?>
1764 + <div class="item"><i class="external alternate icon"></i> <a
1765 + href="https://docs.mainwp.com/dashboard/overview/mainwp-rest-api#rest-api-permissions" target="_blank">REST
1766 + API Permissions</a></div> <?php // NOSONAR -- compatible with help. ?>
1767 + <div class="item"><i class="external alternate icon"></i> <a
1768 + href="https://docs.mainwp.com/dashboard/overview/mainwp-rest-api#disable-rest-api" target="_blank">Disable
1769 + REST API</a></div> <?php // NOSONAR -- compatible with help. ?>
1770 + <div class="item"><i class="external alternate icon"></i> <a
1771 + href="https://docs.mainwp.com/dashboard/overview/mainwp-rest-api#delete-rest-api-keys"
1772 + target="_blank">Delete REST API Key</a></div> <?php // NOSONAR -- compatible with help. ?>
1240 1773 <?php
1241 1774 /**
1242 1775 * Action: mainwp_rest_api_help_item
1243 1776 *
@@ -1253,6 +1786,773 @@
1253 1786 ?>
1254 1787 </div>
1255 1788 <?php
1256 1789 }
1790 + }
1791 +
1792 + /**
1793 + * Render application password table top.
1794 + *
1795 + * @param bool $has_rows Whether there are rows to display.
1796 + */
1797 + public static function render_application_passwords_table_top( $has_rows = true ) {
1798 + // Align with Team Control caps: create_application_passwords, delete_application_passwords.
1799 + $can_add = static::$application_passwords->can_create_application_passwords();
1800 + $can_revoke = static::$application_passwords->can_delete_application_passwords();
1801 +
1802 + if ( ! $can_add && ! $can_revoke ) {
1803 + return;
1804 + }
1805 +
1806 + if ( ! $has_rows ) {
1807 + return;
1808 + }
1809 + ?>
1810 + <div class="mainwp-sub-header">
1811 + <div class="ui grid">
1812 + <div class="equal width row ui mini form">
1813 + <div class="middle aligned column">
1814 + <?php if ( $can_add ) : ?>
1815 + <button type="button" class="ui mini green button"
1816 + id="mainwp-create-application-password-button-top"><?php esc_html_e( 'Add Application Password', 'mainwp' ); ?></button>
1817 + <?php endif; ?>
1818 + </div>
1819 + <div class="right aligned middle aligned column">
1820 + <?php if ( $can_revoke ) : ?>
1821 + <button class="ui mini grey basic button disabled"
1822 + id="mainwp-do-application-passwords-bulk-actions"><?php esc_html_e( 'Revoke Selected Application Passwords', 'mainwp' ); ?></button>
1823 + <?php endif; ?>
1824 + </div>
1825 + </div>
1826 + </div>
1827 + </div>
1828 + <?php
1829 + }
1830 +
1831 + /**
1832 + * Render Application Passwords page.
1833 + */
1834 + public static function render_application_passwords() {
1835 + // Silent check to avoid echoing permission messages on access control.
1836 + $can_access = static::$application_passwords->can_access_application_passwords();
1837 +
1838 + if ( ! $can_access ) {
1839 + static::render_permission_denied_ui(
1840 + esc_html__( 'Application Passwords', 'mainwp' ),
1841 + esc_html__( 'You do not have permission to view Application Passwords. If you need access to this page please contact the dashboard administrator.', 'mainwp' )
1842 + );
1843 + return;
1844 + }
1845 +
1846 + $page_title = 'Application Passwords';
1847 + $context = static::get_app_passwords_context();
1848 + $rows = static::get_application_password_rows( $context );
1849 +
1850 + static::render_header( $page_title );
1851 + static::render_application_passwords_table_top( ! empty( $rows ) );
1852 +
1853 + $current_user_name = ( $context['current_user'] instanceof \WP_User ) ? (string) $context['user_login'] : '';
1854 + $user_id = (int) $context['user_id'];
1855 + $show_user_col = ! empty( $context['can_view_all'] );
1856 + ?>
1857 + <div id="rest-application-passwords-settings" class="ui padded segment"
1858 + data-current-user-name="<?php echo esc_attr( $current_user_name ); ?>"
1859 + data-current-user-id="<?php echo esc_attr( $user_id ); ?>">
1860 + <?php static::render_application_passwords_messages( $rows ); ?>
1861 + <?php static::render_application_passwords_table( $rows, (bool) $show_user_col ); ?>
1862 + <?php static::create_application_password_modal(); ?>
1863 + <?php static::success_application_password_modal(); ?>
1864 + <?php static::edit_application_password_modal(); ?>
1865 +
1866 + <script type="text/javascript">
1867 + jQuery(function ($) {
1868 + var created_index = <?php echo $show_user_col ? 3 : 2; ?>;
1869 +
1870 + window.mainwp_app_passwords_table = $('#mainwp-application-password-table').DataTable({
1871 + pageLength: 10,
1872 + lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
1873 + stateSave: true,
1874 + order: [[created_index, 'desc']],
1875 + columnDefs: [{
1876 + targets: 'no-sort',
1877 + orderable: false
1878 + }],
1879 + initComplete: function () {
1880 + $('#mainwp-application-password-table .ui.checkbox').checkbox();
1881 + if (typeof mainwp_table_check_columns_init === 'function') {
1882 + mainwp_table_check_columns_init();
1883 + }
1884 + },
1885 + drawCallback: function () {
1886 + $('#mainwp-application-password-table .ui.checkbox').checkbox();
1887 + if (typeof mainwp_table_check_columns_init === 'function') {
1888 + mainwp_table_check_columns_init();
1889 + }
1890 + }
1891 + });
1892 + });
1893 + </script>
1894 + </div>
1895 + <?php
1896 + static::render_footer();
1897 + }
1898 +
1899 + /**
1900 + * Render create application password modal.
1901 + */
1902 + public static function create_application_password_modal() {
1903 + ?>
1904 + <div class="ui small modal" id="mainwp-create-application-password-modal">
1905 + <i class="ui close icon"></i>
1906 + <div class="header"><?php esc_html_e( 'Add Application Password', 'mainwp' ); ?></div>
1907 + <div class="content">
1908 + <div class="ui form">
1909 + <div class="field">
1910 + <label
1911 + for="mainwp-app-password-name-input"><?php esc_html_e( 'Application Password Name', 'mainwp' ); ?></label>
1912 + <input type="text" name="app_password_name" id="mainwp-app-password-name-input"
1913 + placeholder="<?php esc_attr_e( 'e.g. MCP Client', 'mainwp' ); ?>" />
1914 + <span
1915 + class="ui small text"><?php esc_html_e( 'Enter a name to help you identify this application password.', 'mainwp' ); ?></span>
1916 + </div>
1917 + </div>
1918 + </div>
1919 + <div class="actions">
1920 + <button class="ui green ok button" id="mainwp-create-app-password-submit">
1921 + <?php esc_html_e( 'Create', 'mainwp' ); ?>
1922 + </button>
1923 +
1924 + </div>
1925 + </div>
1926 + <?php
1927 + }
1928 +
1929 + /**
1930 + * Render success application password modal.
1931 + */
1932 + public static function success_application_password_modal() {
1933 + ?>
1934 + <div class="ui small modal" id="mainwp-application-password-success-modal">
1935 + <div class="header">
1936 + <strong id="app-pass-success-name"></strong> <?php esc_html_e( 'Application Password Created', 'mainwp' ); ?>
1937 + </div>
1938 + <div class="content">
1939 + <div class="ui message info">
1940 + <?php esc_html_e( 'Be sure to save this in a safe location. You will not be able to retrieve it.', 'mainwp' ); ?>
1941 + </div>
1942 + <?php esc_html_e( 'Your new password is:', 'mainwp' ); ?>
1943 + <div class="ui grid">
1944 + <div class="thirteen wide middle aligned column">
1945 + <div class="ui fluid input">
1946 + <input type="text" id="app-pass-success-value" readonly="readonly"
1947 + style="font-family: monospace; font-size: 16px; letter-spacing: 2px;" />
1948 + </div>
1949 + </div>
1950 + <div class="three wide middle aligned column">
1951 + <button class="ui green basic fluid button copy-app-password"
1952 + data-tooltip="<?php esc_attr_e( 'Copy to clipboard', 'mainwp' ); ?>" data-position="top center"
1953 + data-inverted="">
1954 + <i class="copy icon"></i> <?php esc_html_e( 'Copy', 'mainwp' ); ?>
1955 + </button>
1956 + </div>
1957 + </div>
1958 + </div>
1959 + <div class="actions">
1960 + <div class="ui green ok button"><?php esc_html_e( 'Done', 'mainwp' ); ?></div>
1961 + </div>
1962 + </div>
1963 + <?php
1964 + }
1965 +
1966 + /**
1967 + * Render edit application password modal.
1968 + */
1969 + public static function edit_application_password_modal() {
1970 + ?>
1971 + <div class="ui small modal" id="mainwp-edit-application-password-modal">
1972 + <div class="header"><?php esc_html_e( 'Edit Application Password', 'mainwp' ); ?></div>
1973 + <div class="content">
1974 + <div class="ui form">
1975 + <div class="field">
1976 + <label for="mainwp-app-password-edit-name-input"><?php esc_html_e( 'Name', 'mainwp' ); ?></label>
1977 + <input type="text" name="app_password_name_edit" id="mainwp-app-password-edit-name-input" />
1978 + <input type="hidden" id="mainwp-app-password-edit-uuid" />
1979 + <input type="hidden" id="mainwp-app-password-edit-user-id" />
1980 + </div>
1981 + </div>
1982 + </div>
1983 + <div class="actions">
1984 + <button class="ui green ok button" id="mainwp-edit-app-password-submit">
1985 + <?php esc_html_e( 'Save', 'mainwp' ); ?>
1986 + </button>
1987 + <button class="ui cancel button"><?php esc_html_e( 'Cancel', 'mainwp' ); ?></button>
1988 + </div>
1989 + </div>
1990 + <?php
1991 + }
1992 +
1993 + /**
1994 + * AJAX handler for creating application password.
1995 + */
1996 + public function ajax_application_password_create() {
1997 + MainWP_Post_Handler::instance()->check_security( 'mainwp_application_password_create' );
1998 +
1999 + if ( ! static::$application_passwords->can_create_application_passwords() ) {
2000 + wp_send_json_error( array( 'message' => __( 'You are not allowed to create application passwords.', 'mainwp' ) ) );
2001 + }
2002 +
2003 + $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; // phpcs:ignore -- ok.
2004 +
2005 + if ( '' === $name ) {
2006 + wp_send_json_error( array( 'message' => __( 'Application name is required.', 'mainwp' ) ) );
2007 + }
2008 +
2009 + $user_id = get_current_user_id();
2010 + $result = static::$application_passwords->create_new_application_password( $user_id, array( 'name' => $name ) );
2011 +
2012 + if ( is_wp_error( $result ) ) {
2013 + wp_send_json_error( array( 'message' => $result->get_error_message() ) );
2014 + }
2015 +
2016 + list( $password, $item ) = $result;
2017 +
2018 + // Format password with spaces for better readability.
2019 + $formatted_password = static::$application_passwords->chunk_password( $password );
2020 +
2021 + wp_send_json_success(
2022 + array(
2023 + 'password' => $formatted_password,
2024 + 'item' => $item,
2025 + )
2026 + );
2027 + }
2028 +
2029 + /**
2030 + * AJAX handler for deleting application password.
2031 + */
2032 + public function ajax_application_password_delete() {
2033 + MainWP_Post_Handler::instance()->check_security( 'mainwp_application_password_delete' );
2034 +
2035 + if ( ! static::$application_passwords->can_delete_application_passwords() ) {
2036 + wp_send_json_error( array( 'message' => __( 'You are not allowed to revoke application passwords.', 'mainwp' ) ) ); // NOSONAR.
2037 + }
2038 +
2039 + $uuid = isset( $_POST['uuid'] ) ? sanitize_text_field( wp_unslash( $_POST['uuid'] ) ) : ''; // phpcs:ignore -- ok.
2040 + $user_id = isset( $_POST['user_id'] ) ? (int) wp_unslash( $_POST['user_id'] ) : 0; // phpcs:ignore -- ok.
2041 +
2042 + if ( '' === $uuid ) {
2043 + wp_send_json_error( array( 'message' => __( 'UUID is required.', 'mainwp' ) ) );
2044 + }
2045 +
2046 + $current_id = get_current_user_id();
2047 + // Only allow targeting others if can manage or can view all.
2048 + $can_target_others = static::$application_passwords->can_manage_application_passwords() || static::$application_passwords->can_view_all_application_passwords();
2049 + $target_user = ( $user_id > 0 && $can_target_others ) ? $user_id : $current_id;
2050 +
2051 + $result = static::$application_passwords->delete_application_password( $target_user, $uuid );
2052 +
2053 + if ( is_wp_error( $result ) ) {
2054 + wp_send_json_error( array( 'message' => $result->get_error_message() ) );
2055 + }
2056 +
2057 + wp_send_json_success( array( 'deleted' => true ) );
2058 + }
2059 +
2060 + /**
2061 + * AJAX handler for deleting multiple application passwords.
2062 + */
2063 + public function ajax_application_password_delete_multiple() { // phpcs:ignore -- NOSONAR.
2064 + MainWP_Post_Handler::instance()->check_security( 'mainwp_application_password_delete_multiple' );
2065 + // Must have delete capability to delete own passwords.
2066 + $can_delete_self = static::$application_passwords->can_delete_application_passwords();
2067 + if ( ! $can_delete_self ) {
2068 + wp_send_json_error( array( 'message' => __( 'You are not allowed to revoke application passwords.', 'mainwp' ) ) );
2069 + }
2070 +
2071 + // Only allow targeting others if can manage or can view all.
2072 + $can_target_others = static::$application_passwords->can_manage_application_passwords() || static::$application_passwords->can_view_all_application_passwords();
2073 +
2074 + // phpcs:disable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2075 + $items = isset( $_POST['items'] ) && is_array( $_POST['items'] ) ? wp_unslash( $_POST['items'] ) : array();
2076 + $uuids = isset( $_POST['uuids'] ) && is_array( $_POST['uuids'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['uuids'] ) ) : array();
2077 + // phpcs:enable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2078 +
2079 + $current_id = get_current_user_id();
2080 + $deleted = 0;
2081 + $errors = array();
2082 +
2083 + if ( is_array( $items ) && ! empty( $items ) ) {
2084 + foreach ( (array) $items as $it ) {
2085 + $uuid = isset( $it['uuid'] ) ? sanitize_text_field( $it['uuid'] ) : '';
2086 + $user_id = isset( $it['user_id'] ) ? (int) $it['user_id'] : 0;
2087 +
2088 + if ( '' === $uuid ) {
2089 + continue;
2090 + }
2091 +
2092 + $target_user = ( $user_id > 0 && $can_target_others ) ? $user_id : $current_id;
2093 + $result = static::$application_passwords->delete_application_password( $target_user, $uuid );
2094 +
2095 + if ( is_wp_error( $result ) ) {
2096 + $errors[] = $result->get_error_message();
2097 + } else {
2098 + ++$deleted;
2099 + }
2100 + }
2101 + } else {
2102 + $uuids = is_array( $uuids ) ? array_map( 'sanitize_text_field', $uuids ) : array();
2103 + if ( empty( $uuids ) ) {
2104 + wp_send_json_error( array( 'message' => __( 'No passwords selected.', 'mainwp' ) ) );
2105 + }
2106 +
2107 + foreach ( $uuids as $uuid ) {
2108 + $result = static::$application_passwords->delete_application_password( $current_id, $uuid );
2109 +
2110 + if ( is_wp_error( $result ) ) {
2111 + $errors[] = $result->get_error_message();
2112 + } else {
2113 + ++$deleted;
2114 + }
2115 + }
2116 + }
2117 +
2118 + if ( ! empty( $errors ) ) {
2119 + wp_send_json_error( array( 'message' => implode( ', ', $errors ) ) );
2120 + }
2121 +
2122 + wp_send_json_success( array( 'deleted' => $deleted ) );
2123 + }
2124 +
2125 + /**
2126 + * AJAX handler for deleting all application passwords.
2127 + */
2128 + public function ajax_application_password_delete_all() {
2129 + MainWP_Post_Handler::instance()->check_security( 'mainwp_application_password_delete_all' );
2130 +
2131 + if ( ! static::$application_passwords->can_delete_application_passwords() ) {
2132 + wp_send_json_error( array( 'message' => __( 'You are not allowed to revoke application passwords.', 'mainwp' ) ) );
2133 + }
2134 +
2135 + $user_id = get_current_user_id();
2136 + $result = static::$application_passwords->delete_all_application_passwords( $user_id );
2137 +
2138 + if ( is_wp_error( $result ) ) {
2139 + wp_send_json_error( array( 'message' => $result->get_error_message() ) );
2140 + }
2141 +
2142 + wp_send_json_success( array( 'deleted' => $result ) );
2143 + }
2144 +
2145 + /**
2146 + * AJAX handler for updating (renaming) an application password.
2147 + */
2148 + public function ajax_application_password_update() { // phpcs:ignore -- NOSONAR.
2149 + MainWP_Post_Handler::instance()->check_security( 'mainwp_application_password_update' );
2150 + // Must have edit capability to edit own passwords.
2151 + $can_edit_self = static::$application_passwords->can_edit_application_passwords();
2152 + if ( ! $can_edit_self ) {
2153 + wp_send_json_error( array( 'message' => __( 'You are not allowed to edit application passwords.', 'mainwp' ) ) );
2154 + }
2155 +
2156 + // phpcs:disable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2157 + $uuid = isset( $_POST['uuid'] ) ? sanitize_text_field( wp_unslash( $_POST['uuid'] ) ) : '';
2158 + $user_id = isset( $_POST['user_id'] ) ? (int) wp_unslash( $_POST['user_id'] ) : 0; // phpcs:ignore -- ok.
2159 + $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
2160 +
2161 + // phpcs:enable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2162 +
2163 + if ( '' === $uuid || '' === $name ) {
2164 + wp_send_json_error( array( 'message' => __( 'UUID and Name are required.', 'mainwp' ) ) );
2165 + }
2166 +
2167 + $current_id = get_current_user_id();
2168 + // Only allow targeting others if can manage or can view all.
2169 + $can_edit_others = static::$application_passwords->can_manage_application_passwords() || static::$application_passwords->can_view_all_application_passwords();
2170 + $target_user = ( $user_id > 0 && $can_edit_others ) ? $user_id : $current_id;
2171 + $result = static::$application_passwords->update_application_password( $target_user, $uuid, array( 'name' => $name ) );
2172 +
2173 + if ( is_wp_error( $result ) ) {
2174 + wp_send_json_error( array( 'message' => $result->get_error_message() ) );
2175 + }
2176 +
2177 + $updated_item = static::$application_passwords->get_user_application_password( $target_user, $uuid );
2178 + wp_send_json_success(
2179 + array(
2180 + 'updated' => true,
2181 + 'item' => $updated_item,
2182 + )
2183 + );
2184 + }
2185 +
2186 + /**
2187 + * Get the context for application passwords.
2188 + *
2189 + * @return array The context.
2190 + */
2191 + protected static function get_app_passwords_context() {
2192 + $user_id = get_current_user_id();
2193 + $current_user = wp_get_current_user();
2194 + $admin_email = get_option( 'admin_email' );
2195 + $display_name = '';
2196 + $user_login = '';
2197 +
2198 + if ( $current_user instanceof \WP_User ) {
2199 + $display_name = ! empty( $current_user->display_name ) ? $current_user->display_name : $current_user->user_login;
2200 + $user_login = $current_user->user_login;
2201 + }
2202 +
2203 + $can_view_all = static::$application_passwords->can_view_all_application_passwords();
2204 +
2205 + return array(
2206 + 'user_id' => (int) $user_id,
2207 + 'current_user' => $current_user,
2208 + 'display_name' => $display_name,
2209 + 'admin_email' => (string) $admin_email,
2210 + 'user_login' => $user_login,
2211 + 'can_view_all' => (bool) $can_view_all,
2212 + );
2213 + }
2214 +
2215 + /**
2216 + * Get the application password rows.
2217 + *
2218 + * @param array $context The context.
2219 + *
2220 + * @return array The application password rows.
2221 + */
2222 + protected static function get_application_password_rows( $context ) {
2223 + $can_view_all = static::$application_passwords->can_view_all_application_passwords();
2224 +
2225 + // Only allow viewing all users' passwords when can_view_all is granted.
2226 + if ( $can_view_all ) {
2227 + return static::get_all_users_application_password_rows();
2228 + }
2229 +
2230 + return static::get_single_user_application_password_rows(
2231 + (int) $context['user_id'],
2232 + $context['user_login'],
2233 + $context['current_user'] instanceof \WP_User ? (string) $context['display_name'] : ''
2234 + );
2235 + }
2236 +
2237 + /**
2238 + * Get all users application password rows.
2239 + *
2240 + * @return array The application password rows.
2241 + */
2242 + protected static function get_all_users_application_password_rows() {
2243 + $rows = array();
2244 + $users = get_users(
2245 + array(
2246 + 'fields' => array( 'ID', 'display_name', 'user_email', 'user_login' ),
2247 + )
2248 + );
2249 +
2250 + foreach ( $users as $u ) {
2251 + $pwds = static::$application_passwords->get_user_application_passwords( $u->ID );
2252 + if ( empty( $pwds ) ) {
2253 + continue;
2254 + }
2255 +
2256 + $user_name = ! empty( $u->display_name ) ? $u->display_name : $u->user_login;
2257 +
2258 + foreach ( $pwds as $pwd_item ) {
2259 + $rows[] = static::decorate_password_row(
2260 + $pwd_item,
2261 + (int) $u->ID,
2262 + (string) $u->user_login,
2263 + (string) $user_name,
2264 + (string) $u->user_email
2265 + );
2266 + }
2267 + }
2268 +
2269 + return $rows;
2270 + }
2271 +
2272 + /**
2273 + * Get a single user application password rows.
2274 + *
2275 + * @param int $user_id The user ID.
2276 + * @param string $user_login The user login.
2277 + * @param string $display_name The display name.
2278 + *
2279 + * @return array The application password rows.
2280 + */
2281 + protected static function get_single_user_application_password_rows( $user_id, $user_login, $display_name ) {
2282 + $rows = array();
2283 + $passwords = static::$application_passwords->get_user_application_passwords( $user_id );
2284 +
2285 + foreach ( $passwords as $pwd_item ) {
2286 + $rows[] = static::decorate_password_row(
2287 + $pwd_item,
2288 + (int) $user_id,
2289 + (string) $user_login,
2290 + (string) $display_name,
2291 + ''
2292 + );
2293 + }
2294 +
2295 + return $rows;
2296 + }
2297 +
2298 + /**
2299 + * Decorate a password row.
2300 + *
2301 + * @param array $pwd_item The password item.
2302 + * @param int $user_id The user ID.
2303 + * @param string $user_login The user login.
2304 + * @param string $user_name The user name.
2305 + * @param string $user_email The user email.
2306 + *
2307 + * @return array The decorated password row.
2308 + */
2309 + protected static function decorate_password_row( $pwd_item, $user_id, $user_login, $user_name, $user_email ) {
2310 + $pwd_item['user_id'] = (int) $user_id;
2311 + $pwd_item['user_login'] = (string) $user_login;
2312 + $pwd_item['user_full_name'] = (string) $user_name;
2313 +
2314 + if ( '' !== $user_email ) {
2315 + $pwd_item['user_email'] = (string) $user_email;
2316 + }
2317 +
2318 + return $pwd_item;
2319 + }
2320 +
2321 + /**
2322 + * Render application passwords messages.
2323 + *
2324 + * @param array $rows The application password rows.
2325 + */
2326 + protected static function render_application_passwords_messages( $rows ) {
2327 + if ( empty( $rows ) ) {
2328 + if ( MainWP_Utility::show_mainwp_message( 'notice', 'mainwp-app-passwords-welcome-message' ) ) {
2329 + ?>
2330 + <div class="ui icon message mainwp-welcome-message" style="margin-bottom:0;">
2331 + <em data-emoji=":wave:" class="big"></em>
2332 + <div class="content">
2333 + <div class="ui massive header"><?php esc_html_e( 'Get started with MainWP Abilities API / MCP', 'mainwp' ); ?>
2334 + </div>
2335 + <p><?php esc_html_e( 'You haven\'t created any Application Passwords. MainWP Abilities API / MCP uses WordPress Application Passwords (not the MainWP API Key).', 'mainwp' ); ?>
2336 + </p>
2337 + <p><?php esc_html_e( 'Create a new Application Password (recommended: name it after the client/tool), then use it as the credential when connecting.', 'mainwp' ); ?>
2338 + </p>
2339 + </div>
2340 + <i class="close icon mainwp-notice-dismiss" notice-id="mainwp-app-passwords-welcome-message"></i>
2341 + </div>
2342 + <?php
2343 + }
2344 + return;
2345 + }
2346 +
2347 + if ( MainWP_Utility::show_mainwp_message( 'notice', 'mainwp-app-passwords-info-message' ) ) {
2348 + ?>
2349 + <div class="ui info message">
2350 + <i class="close icon mainwp-notice-dismiss" notice-id="mainwp-app-passwords-info-message"></i>
2351 + <?php
2352 + /* translators: 1: Opening anchor tag for Abilities API link. 2: Closing anchor tag. */
2353 + printf( esc_html__( '%1$sMainWP Abilities API%2$s / MCP uses WordPress Application Passwords so you can grant tool access without sharing your account password. Create a dedicated Application Password (for example "MCP Client") and revoke it whenever you no longer need it. This does not affect your regular login password.', 'mainwp' ), '<a href="' . esc_url( 'https://docs.mainwp.com/api-reference/abilities-api/overview' ) . '" target="_blank">', '</a>' );
2354 + ?>
2355 + </div>
2356 + <?php
2357 + }
2358 + }
2359 +
2360 + /**
2361 + * Render the application passwords table.
2362 + *
2363 + * @param array $rows The application password rows.
2364 + * @param bool $show_user_col Whether to show the username column.
2365 + */
2366 + protected static function render_application_passwords_table( $rows, $show_user_col ) {
2367 + if ( empty( $rows ) ) {
2368 + MainWP_UI::render_empty_page_placeholder(
2369 + esc_html__( 'No Application Passwords yet', 'mainwp' ),
2370 + esc_html__( 'Create your first application password to allow external applications to authenticate with MainWP.', 'mainwp' ),
2371 + '<em data-emoji=":key:" class="big"></em>'
2372 + );
2373 + if ( static::$application_passwords->can_create_application_passwords() ) {
2374 + ?>
2375 + <div class="ui center aligned segment">
2376 + <button type="button" class="ui green button" id="mainwp-create-application-password-button"><?php esc_html_e( 'Add Application Password', 'mainwp' ); ?></button>
2377 + </div>
2378 + <?php
2379 + }
2380 + return;
2381 + }
2382 +
2383 + // Align revoke (delete) permission with delete_application_passwords capability.
2384 + $can_revoke = static::$application_passwords->can_delete_application_passwords();
2385 + $can_edit = static::$application_passwords->can_edit_application_passwords();
2386 + ?>
2387 + <div id="mainwp-message-zone-app-passwords" style="display:none;"></div>
2388 + <div class="content active application-passwords-list-table-wrapper">
2389 + <table id="mainwp-application-password-table" class="ui unstackable single line table"
2390 + data-can-manage="<?php echo esc_attr( $can_revoke ? '1' : '0' ); ?>"
2391 + data-can-edit="<?php echo esc_attr( $can_edit ? '1' : '0' ); ?>">
2392 + <thead>
2393 + <tr>
2394 + <th scope="col" class="no-sort collapsing check-column">
2395 + <span class="ui checkbox">
2396 + <input aria-label="<?php esc_attr_e( 'Select all Application Passwords', 'mainwp' ); ?>"
2397 + id="application-password-select-all-top" type="checkbox" />
2398 + </span>
2399 + </th>
2400 + <th scope="col" class="collapsing"><?php esc_html_e( 'Name', 'mainwp' ); ?></th>
2401 +
2402 + <?php if ( $show_user_col ) : ?>
2403 + <th scope="col" class="collapsing mainwp-col-user"><?php esc_html_e( 'Username', 'mainwp' ); ?></th>
2404 + <?php endif; ?>
2405 +
2406 + <th scope="col"><?php esc_html_e( 'Created', 'mainwp' ); ?></th>
2407 + <th scope="col" class="collapsing"><?php esc_html_e( 'Last Used', 'mainwp' ); ?></th>
2408 + <th scope="col" class="collapsing"><?php esc_html_e( 'Last IP', 'mainwp' ); ?></th>
2409 + <th scope="col" class="no-sort collapsing"></th>
2410 + </tr>
2411 + </thead>
2412 + <tbody id="mainwp-application-password-table-body" class="mainwp-application-password-body-table-manage">
2413 + <?php
2414 + foreach ( $rows as $item ) {
2415 + static::render_application_password_row( $item, (bool) $show_user_col, (bool) $can_revoke );
2416 + }
2417 + ?>
2418 + </tbody>
2419 + </table>
2420 + </div>
2421 + <?php
2422 + }
2423 +
2424 + /**
2425 + * Render a single application password row.
2426 + *
2427 + * @param array $item The application password item.
2428 + * @param bool $show_user_col Whether to show the username column.
2429 + * @param bool $can_revoke Whether the current user can revoke application passwords.
2430 + */
2431 + protected static function render_application_password_row( $item, $show_user_col, $can_revoke ) {
2432 + // translators: %s is the application password name.
2433 + $aria_select = esc_attr( sprintf( __( 'Select %s', 'mainwp' ), $item['name'] ) );
2434 + // translators: %s is the application password name.
2435 + $aria_rename = esc_attr( sprintf( __( 'Rename "%s"', 'mainwp' ), $item['name'] ) );
2436 + // translators: %s is the application password name.
2437 + $aria_revoke = esc_attr( sprintf( __( 'Revoke "%s"', 'mainwp' ), $item['name'] ) );
2438 + ?>
2439 + <tr data-uuid="<?php echo esc_attr( $item['uuid'] ); ?>" data-user-id="<?php echo esc_attr( $item['user_id'] ); ?>"
2440 + class="mainwp-application-password-row">
2441 + <td class="check-column">
2442 + <div class="ui checkbox">
2443 + <input type="checkbox" <?php echo $can_revoke ? '' : 'disabled'; ?>
2444 + class="mainwp-application-password-checkbox"
2445 + aria-label="<?php echo $aria_select; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"
2446 + value="<?php echo esc_attr( $item['uuid'] ); ?>"
2447 + data-user-id="<?php echo esc_attr( $item['user_id'] ); ?>" />
2448 + </div>
2449 + </td>
2450 +
2451 + <td><?php echo esc_html( $item['name'] ); ?></td>
2452 +
2453 + <?php if ( $show_user_col ) : ?>
2454 + <td><?php echo esc_html( $item['user_login'] ); ?></td>
2455 + <?php endif; ?>
2456 +
2457 + <td data-order="<?php echo esc_attr( $item['created'] ); ?>">
2458 + <?php echo esc_html( MainWP_Utility::format_timestamp( $item['created'] ) ); ?>
2459 + </td>
2460 +
2461 + <td data-order="<?php echo ! empty( $item['last_used'] ) ? esc_attr( $item['last_used'] ) : 0; ?>">
2462 + <?php
2463 + if ( ! empty( $item['last_used'] ) ) {
2464 + echo '<span data-tooltip="' . esc_attr( MainWP_Utility::format_timestamp( $item['last_used'] ) ) . '" data-position="left center" data-inverted="">' .
2465 + esc_html( MainWP_Utility::time_elapsed_string( $item['last_used'] ) ) .
2466 + '</span>';
2467 + } else {
2468 + echo '&mdash;';
2469 + }
2470 + ?>
2471 + </td>
2472 +
2473 + <td>
2474 + <?php
2475 + if ( ! empty( $item['last_ip'] ) ) {
2476 + echo esc_html( $item['last_ip'] );
2477 + } else {
2478 + echo '&mdash;';
2479 + }
2480 + ?>
2481 + </td>
2482 +
2483 + <td class="right aligned">
2484 + <?php if ( static::$application_passwords->can_edit_application_passwords() ) : ?>
2485 + <button type="button" class="ui mini basic button mainwp-edit-application-password"
2486 + data-uuid="<?php echo esc_attr( $item['uuid'] ); ?>"
2487 + data-user-id="<?php echo esc_attr( $item['user_id'] ); ?>"
2488 + data-name="<?php echo esc_attr( $item['name'] ); ?>"
2489 + aria-label="<?php echo $aria_rename; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>">
2490 + <?php esc_html_e( 'Edit', 'mainwp' ); ?>
2491 + </button>
2492 + <?php endif; ?>
2493 + <?php if ( $can_revoke ) : ?>
2494 + <button type="button" class="ui mini button mainwp-revoke-application-password"
2495 + data-uuid="<?php echo esc_attr( $item['uuid'] ); ?>"
2496 + data-user-id="<?php echo esc_attr( $item['user_id'] ); ?>"
2497 + aria-label="<?php echo $aria_revoke; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>">
2498 + <?php esc_html_e( 'Revoke', 'mainwp' ); ?>
2499 + </button>
2500 + <?php endif; ?>
2501 + </td>
2502 + </tr>
2503 + <?php
2504 + }
2505 +
2506 + /**
2507 + * Determine if user can view REST API.
2508 + *
2509 + * @return bool
2510 + */
2511 + public static function can_access_rest_api() {
2512 + if (
2513 + static::can_view_manage_rest_api() ||
2514 + static::can_create_rest_api_keys() ||
2515 + static::can_delete_rest_api_keys() ||
2516 + static::can_edit_rest_api_keys()
2517 + ) {
2518 + return true;
2519 + }
2520 + return false;
2521 + }
2522 +
2523 + /**
2524 + * Check if the current user can view and manage REST API keys.
2525 + *
2526 + * @return bool
2527 + */
2528 + public static function can_view_manage_rest_api() {
2529 + return \mainwp_current_user_can( 'rest_api', 'manage_rest_api_keys' );
2530 + }
2531 +
2532 + /**
2533 + * Check if the current user can create REST API keys.
2534 + *
2535 + * @return bool
2536 + */
2537 + public static function can_create_rest_api_keys() {
2538 + return \mainwp_current_user_can( 'rest_api', 'create_rest_api_keys' );
2539 + }
2540 +
2541 + /**
2542 + * Check if the current user can delete REST API keys.
2543 + *
2544 + * @return bool
2545 + */
2546 + public static function can_delete_rest_api_keys() {
2547 + return \mainwp_current_user_can( 'rest_api', 'delete_rest_api_keys' );
2548 + }
2549 +
2550 + /**
2551 + * Check if the current user can edit REST API keys.
2552 + *
2553 + * @return bool
2554 + */
2555 + public static function can_edit_rest_api_keys() {
2556 + return \mainwp_current_user_can( 'rest_api', 'edit_rest_api_keys' );
1257 2557 }
1258 2558 }