PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
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 +1942 -286 5.1 → 6.2.1 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,12 +63,21 @@
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 - * This hook allows you to render the REST API page header via the 'mainwp_pageheader_restapi' action.
79 + * Renders the REST API page header via the 'mainwp_pageheader_restapi' action.
59 80 *
60 81 * This hook is normally used in the same context of 'mainwp_getsubpages_restapi'
61 82 */
62 83 add_action( 'mainwp-pageheader-restapi', array( static::get_class_name(), 'render_header' ) );
@@ -61,14 +82,16 @@
61 82 */
62 83 add_action( 'mainwp-pageheader-restapi', array( static::get_class_name(), 'render_header' ) );
63 84
64 85 /**
65 - * This hook allows you to render the REST API page footer via the 'mainwp-pagefooter-restapi' action.
86 + * Renders the REST API page footer via the 'mainwp-pagefooter-restapi' action.
66 87 *
67 88 * This hook is normally used in the same context of 'mainwp-getsubpages-restapi'
68 89 */
69 90 add_action( 'mainwp-pagefooter-restapi', array( static::get_class_name(), 'render_footer' ) );
70 91
92 + add_action( 'mainwp_help_sidebar_content', array( static::get_class_name(), 'mainwp_help_content' ) );
93 +
71 94 add_action( 'admin_init', array( static::get_instance(), 'admin_init' ) );
72 95 }
73 96
74 97 /** Run the export_sites method that exports the Child Sites .csv file */
@@ -73,8 +96,39 @@
73 96
74 97 /** Run the export_sites method that exports the Child Sites .csv file */
75 98 public function admin_init() {
76 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 +
77 131 $this->handle_rest_api_add_new();
78 132 $this->handle_rest_api_edit();
79 133 }
80 134
@@ -84,12 +138,16 @@
84 138 *
85 139 * @uses \MainWP\Dashboard\MainWP_Menu::is_disable_menu_item()
86 140 */
87 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 +
88 146 add_submenu_page(
89 147 'mainwp_tab',
90 - esc_html__( 'REST API', 'mainwp' ),
91 - ' <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>',
92 150 'read',
93 151 'RESTAPI',
94 152 array(
95 153 static::get_class_name(),
@@ -99,10 +157,10 @@
99 157
100 158 if ( ! MainWP_Menu::is_disable_menu_item( 3, 'AddApiKeys' ) ) {
101 159 add_submenu_page(
102 160 'mainwp_tab',
103 - esc_html__( 'Add API Keys', 'mainwp' ),
104 - ' <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>',
105 163 'read',
106 164 'AddApiKeys',
107 165 array(
108 166 static::get_class_name(),
@@ -109,8 +167,21 @@
109 167 'render_rest_api_setings',
110 168 )
111 169 );
112 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 + }
113 184
114 185 /**
115 186 * REST API Subpages
116 187 *
@@ -139,12 +210,12 @@
139 210 */
140 211 public static function init_subpages_menu() {
141 212 ?>
142 213 <div id="menu-mainwp-RESTAPI" class="mainwp-submenu-wrapper">
143 - <div class="wp-submenu sub-open" style="">
214 + <div class="wp-submenu sub-open">
144 215 <div class="mainwp_boxout">
145 216 <div class="mainwp_boxoutin"></div>
146 - <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>
147 218 <?php
148 219 if ( isset( static::$subPages ) && is_array( static::$subPages ) && ! empty( static::$subPages ) ) {
149 220 foreach ( static::$subPages as $subPage ) {
150 221 if ( MainWP_Menu::is_disable_menu_item( 3, 'RESTAPI' . $subPage['slug'] ) ) {
@@ -169,39 +240,98 @@
169 240 *
170 241 * @param array $subPages SubPages Array.
171 242 */
172 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 + }
173 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 +
174 261 MainWP_Menu::add_left_menu(
175 262 array(
176 - 'title' => esc_html__( 'REST API', 'mainwp' ),
263 + 'title' => esc_html__( 'Access', 'mainwp' ),
177 264 'parent_key' => 'mainwp_tab',
178 265 'slug' => 'RESTAPI',
179 - 'href' => 'admin.php?page=RESTAPI',
266 + 'href' => $root_href,
267 + 'id' => 'mainwp-restapi-active-item',
180 268 'icon' => '<div class="mainwp-api-icon">API</div>',
181 269 ),
182 270 0
183 271 );
184 272
185 - $init_sub_subleftmenu = array(
186 - array(
187 - 'title' => esc_html__( 'Manage API Keys', 'mainwp' ),
188 - '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',
189 289 'href' => 'admin.php?page=RESTAPI',
190 290 'slug' => 'RESTAPI',
191 - 'right' => 'manage_restapi',
192 - ),
291 + 'right' => '',
292 + );
193 293
194 - array(
195 - 'title' => esc_html__( 'Add API Keys', 'mainwp' ),
196 - 'parent_key' => 'RESTAPI',
197 - 'href' => 'admin.php?page=AddApiKeys',
198 - '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',
199 322 'right' => '',
200 - ),
323 + );
324 + }
325 +
326 + // Third-level items (actual links).
327 + $init_sub_subleftmenu = array_merge(
328 + $sub_rest_api,
329 + $sub_application_passwords
201 330 );
202 331
203 - 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' );
204 334 foreach ( $init_sub_subleftmenu as $item ) {
205 335 if ( MainWP_Menu::is_disable_menu_item( 3, $item['slug'] ) ) {
206 336 continue;
207 337 }
@@ -223,8 +353,13 @@
223 353 $submit = true;
224 354 }
225 355
226 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 + }
227 362 $all_keys = static::check_rest_api_updates();
228 363
229 364 if ( ! is_array( $all_keys ) ) {
230 365 $all_keys = array();
@@ -236,20 +371,64 @@
236 371 $enabled = ! empty( $_POST['mainwp_enable_rest_api'] ) ? 1 : 0;
237 372 $pers = ! empty( $_POST['mainwp_rest_api_key_edit_pers'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_edit_pers'] ) ) : '';
238 373
239 374 // hash the password.
240 - $consumer_key_hashed = wp_hash_password( $consumer_key );
241 - $consumer_secret_hashed = wp_hash_password( $consumer_secret );
242 - $all_keys[ $consumer_key ] = array(
243 - 'ck_hashed' => $consumer_key_hashed,
244 - 'cs' => $consumer_secret_hashed,
245 - 'desc' => $desc,
246 - 'enabled' => $enabled,
247 - 'perms' => $pers,
375 + $consumer_key_hashed = wp_hash_password( $consumer_key );
376 + $consumer_secret_hashed = wp_hash_password( $consumer_secret );
377 +
378 + if ( ! empty( $_POST['mainwp_rest_api_keys_compatible_v1'] ) ) {
379 + $all_keys[ $consumer_key ] = array(
380 + 'ck_hashed' => $consumer_key_hashed,
381 + 'cs' => $consumer_secret_hashed,
382 + 'desc' => $desc,
383 + 'enabled' => $enabled,
384 + 'perms' => $pers,
385 + );
386 + // store the data.
387 + MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
388 + }
389 +
390 + // compatible with version 2.
391 + $scope = 'read';
392 + if ( ! empty( $pers ) ) {
393 + $pers_list = explode( ',', $pers );
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 ) {
398 + $scope = 'read_write';
399 + } elseif ( $has_write ) {
400 + $scope = 'write';
401 + } elseif ( $has_read ) {
402 + $scope = 'read';
403 + }
404 + }
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 + )
248 430 );
249 - // store the data.
250 - MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
251 - wp_safe_redirect( esc_url( admin_url( 'admin.php?page=RESTAPI&message=created' ) ) );
252 431 exit();
253 432 }
254 433 // phpcs:enable
255 434 }
@@ -261,68 +440,128 @@
261 440 */
262 441 public function handle_rest_api_edit() { // phpcs:ignore -- NOSONAR - complex.
263 442
264 443 $edit_id = isset( $_POST['editkey_id'] ) ? sanitize_text_field( wp_unslash( $_POST['editkey_id'] ) ) : false;
444 +
265 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 + }
451 +
266 452 $save = false;
267 453 $updated = false;
268 454 if ( ! empty( $edit_id ) ) {
269 - $all_keys = get_option( 'mainwp_rest_api_keys', false );
270 - if ( is_array( $all_keys ) && isset( $all_keys[ $edit_id ] ) ) {
271 - $item = $all_keys[ $edit_id ];
272 - if ( is_array( $item ) && isset( $item['cs'] ) ) {
273 - $item['desc'] = isset( $_POST['mainwp_rest_api_key_desc'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_desc'] ) ) : '';
274 - $item['enabled'] = ! empty( $_POST['mainwp_enable_rest_api'] ) ? 1 : 0;
275 - $item['perms'] = ! empty( $_POST['mainwp_rest_api_key_edit_pers'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_edit_pers'] ) ) : '';
455 + if ( ! empty( $_POST['rest_v2_edit'] ) ) {
456 + $key_id = intval( $edit_id );
457 + $desc = isset( $_POST['mainwp_rest_api_key_desc'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_desc'] ) ) : '';
458 + $enabled = ! empty( $_POST['mainwp_enable_rest_api'] ) ? 1 : 0;
459 + $pers = ! empty( $_POST['mainwp_rest_api_key_edit_pers'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_edit_pers'] ) ) : '';
460 + $scope = 'read';
461 + if ( ! empty( $pers ) ) {
462 + $pers_list = explode( ',', $pers );
463 + $has_read = in_array( 'r', $pers_list );
464 + $has_write = in_array( 'w', $pers_list ) || in_array( 'd', $pers_list );
276 465
277 - $all_keys[ $edit_id ] = $item;
278 - $updated = true;
279 - $save = true;
280 - } else {
281 - unset( $all_keys[ $edit_id ] ); // delete incorrect key.
282 - $save = true;
466 + if ( $has_read && $has_write ) {
467 + $scope = 'read_write';
468 + } elseif ( $has_write ) {
469 + $scope = 'write';
470 + } elseif ( $has_read ) {
471 + $scope = 'read';
472 + }
283 473 }
474 +
475 + MainWP_DB::instance()->update_rest_api_key( $key_id, $scope, $desc, $enabled );
476 +
477 + $updated = true;
478 + $save = true;
479 + } else {
480 + $edit_id = sanitize_text_field( $edit_id );
481 + $all_keys = get_option( 'mainwp_rest_api_keys', false );
482 + if ( is_array( $all_keys ) && isset( $all_keys[ $edit_id ] ) ) {
483 + $item = $all_keys[ $edit_id ];
484 + if ( is_array( $item ) && isset( $item['cs'] ) ) {
485 + $item['desc'] = isset( $_POST['mainwp_rest_api_key_desc'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_desc'] ) ) : '';
486 + $item['enabled'] = ! empty( $_POST['mainwp_enable_rest_api'] ) ? 1 : 0;
487 + $item['perms'] = ! empty( $_POST['mainwp_rest_api_key_edit_pers'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_edit_pers'] ) ) : '';
488 +
489 + $all_keys[ $edit_id ] = $item;
490 + $updated = true;
491 + $save = true;
492 + } else {
493 + unset( $all_keys[ $edit_id ] ); // delete incorrect key.
494 + $save = true;
495 + }
496 +
497 + if ( $save ) {
498 + MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
499 + }
500 + }
284 501 }
285 502 }
286 503
287 - if ( $save ) {
288 - MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
289 - }
290 -
291 504 $msg = '';
292 505 if ( $updated ) {
293 506 $msg = '&message=saved';
294 507 }
295 - wp_safe_redirect( esc_url( admin_url( 'admin.php?page=RESTAPI' . $msg ) ) );
508 +
509 + $this->invalidate_warm_cache();
510 +
511 + wp_safe_redirect( admin_url( 'admin.php?page=RESTAPI' . $msg ) ); //phpcs:ignore -- ok.
296 512 exit();
297 513 }
298 514 }
299 515
300 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 + /**
301 524 * Method ajax_rest_api_remove_keys()
302 525 *
303 526 * Remove API Key.
304 527 */
305 - public function ajax_rest_api_remove_keys() {
528 + public function ajax_rest_api_remove_keys() { //phpcs:ignore -- NOSONAR - complex.
306 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 + }
307 540 $ret = array( 'success' => false );
308 541 $cons_key_id = isset( $_POST['keyId'] ) ? urldecode( wp_unslash( $_POST['keyId'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
542 + $is_v2 = isset( $_POST['api_ver'] ) && 'v2' === wp_unslash( $_POST['api_ver'] ) ? true : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
309 543
310 544 if ( ! empty( $cons_key_id ) ) {
311 - $save = false;
312 - $all_keys = get_option( 'mainwp_rest_api_keys', false );
313 - if ( is_array( $all_keys ) && isset( $all_keys[ $cons_key_id ] ) ) {
314 - $item = $all_keys[ $cons_key_id ];
315 - if ( is_array( $item ) && isset( $item['cs'] ) ) {
316 - unset( $all_keys[ $cons_key_id ] ); // delete key.
317 - $save = true;
545 + if ( $is_v2 ) {
546 + MainWP_DB::instance()->remove_rest_api_key( $cons_key_id );
547 + } else {
548 + $save = false;
549 + $all_keys = get_option( 'mainwp_rest_api_keys', false );
550 + if ( is_array( $all_keys ) && isset( $all_keys[ $cons_key_id ] ) ) {
551 + $item = $all_keys[ $cons_key_id ];
552 + if ( is_array( $item ) && isset( $item['cs'] ) ) {
553 + unset( $all_keys[ $cons_key_id ] ); // delete key.
554 + $save = true;
555 + }
318 556 }
557 + if ( $save ) {
558 + MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
559 + }
319 560 }
320 - if ( $save ) {
321 - MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
322 - }
323 561 $ret['success'] = 'SUCCESS';
324 562 $ret['result'] = esc_html__( 'REST API Key deleted successfully.', 'mainwp' );
563 + $this->invalidate_warm_cache();
325 564 } else {
326 565 $ret['error'] = esc_html__( 'REST API Key ID empty.', 'mainwp' );
327 566 }
328 567 echo wp_json_encode( $ret );
@@ -341,9 +580,9 @@
341 580 */
342 581 public static function render_header( $shownPage = '' ) { // phpcs:ignore -- NOSONAR - complex.
343 582
344 583 $params = array(
345 - 'title' => esc_html__( 'REST API', 'mainwp' ),
584 + 'title' => esc_html__( 'API Access', 'mainwp' ),
346 585 );
347 586
348 587 MainWP_UI::render_top_header( $params );
349 588
@@ -348,21 +587,25 @@
348 587 MainWP_UI::render_top_header( $params );
349 588
350 589 $renderItems = array();
351 590
352 - 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() ) {
353 593 $renderItems[] = array(
354 - 'title' => esc_html__( 'Manage API Keys', 'mainwp' ),
594 + 'title' => esc_html__( 'API Keys', 'mainwp' ),
355 595 'href' => 'admin.php?page=RESTAPI',
356 596 'active' => ( '' === $shownPage ) ? true : false,
357 597 );
358 598 }
359 599
360 - if ( ! MainWP_Menu::is_disable_menu_item( 3, 'AddApiKeys' ) ) {
361 - if ( isset( $_GET['editkey'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
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() ) ) {
602 + if ( isset( $_GET['editkey'] ) && ! empty( $_GET['editkey'] ) && isset( $_GET['_opennonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_opennonce'] ), 'mainwp-admin-nonce' ) ) {
603 + // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
604 + $ver = isset( $_GET['rest_ver'] ) && ! empty( $_GET['rest_ver'] ) ? '&rest_ver=' . intval( $_GET['rest_ver'] ) : '';
362 605 $renderItems[] = array(
363 606 'title' => esc_html__( 'Edit API Keys', 'mainwp' ),
364 - 'href' => 'admin.php?page=AddApiKeys&editkey=' . esc_url( wp_unslash( $_GET['editkey'] ) ), // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
607 + 'href' => 'admin.php?page=AddApiKeys&editkey=' . esc_url( wp_unslash( $_GET['editkey'] ) ) . $ver . '&_opennonce=' . esc_html( wp_create_nonce( 'mainwp-admin-nonce' ) ), // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
365 608 'active' => ( 'Edit' === $shownPage ) ? true : false,
366 609 );
367 610 }
368 611 $renderItems[] = array(
@@ -371,8 +614,20 @@
371 614 'active' => ( 'Settings' === $shownPage ) ? true : false,
372 615 );
373 616 }
374 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 +
375 630 if ( isset( static::$subPages ) && is_array( static::$subPages ) ) {
376 631 foreach ( static::$subPages as $subPage ) {
377 632 if ( MainWP_Menu::is_disable_menu_item( 3, 'RESTAPI' . $subPage['slug'] ) ) {
378 633 continue;
@@ -394,44 +649,87 @@
394 649 public static function render_footer() {
395 650 echo '</div>';
396 651 }
397 652
398 - /** Render REST API SubPage */
399 - public static function render_all_api_keys() { // phpcs:ignore -- NOSONAR - complex.
400 - if ( ! mainwp_current_user_have_right( 'dashboard', 'manage_dashboard_restapi' ) ) {
401 - mainwp_do_not_have_permissions( esc_html__( 'manage dashboard REST API', 'mainwp' ) );
402 - return;
403 - }
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 );
662 + ?>
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>
694 + <?php
695 + static::render_footer();
696 + }
404 697
405 - $all_keys = static::check_rest_api_updates();
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();
406 707
407 708 if ( ! is_array( $all_keys ) ) {
408 709 $all_keys = array();
409 710 }
410 - static::render_header();
411 - static::render_table_top();
412 - if ( ! static::check_rest_api_enabled() ) {
711 +
712 + if ( ! empty( $all_keys ) ) {
713 + $write_delete = esc_html__( 'Write & Delete', 'mainwp' ); // NOSONAR - ok.
413 714 ?>
414 - <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>
415 - <?php
416 - }
417 715
418 - ?>
419 - <div id="mainwp-rest-api-keys" class="ui segment">
420 - <div class="ui message" id="mainwp-message-zone-apikeys" style="display:none;"></div>
421 - <?php static::show_messages(); ?>
422 - <table id="mainwp-rest-api-keys-table" class="ui unstackable table">
716 + <table id="mainwp-rest-api-keys-table" class="ui unstackable single linetable">
423 717 <thead>
424 718 <tr>
425 - <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>
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>
426 722 <th scope="col" class="collapsing"><?php esc_html_e( 'Status', 'mainwp' ); ?></th>
427 - <th scope="col" ><?php esc_html_e( 'Permissions', 'mainwp' ); ?></th>
428 - <th scope="col" ><?php esc_html_e( 'Description', 'mainwp' ); ?></th>
723 + <th scope="col"><?php esc_html_e( 'API Key', 'mainwp' ); ?></th>
724 + <th scope="col" class="collapsing"><?php esc_html_e( 'Permissions', 'mainwp' ); ?></th>
429 725 <th scope="col" class="no-sort collapsing"><?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></th>
430 - <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; ?>
431 729 </tr>
432 730 </thead>
433 - <tbody id="mainwp-rest-api-body-table">
731 + <tbody id="mainwp-rest-api-body-table" class="mainwp-rest-api-body-table-manage">
434 732 <?php
435 733 foreach ( $all_keys as $ck => $item ) {
436 734 if ( ! is_array( $item ) ) {
437 735 continue;
@@ -464,54 +762,237 @@
464 762 }
465 763 }
466 764
467 765 ?>
468 - <tr key-ck-id="<?php echo esc_html( $endcoded_ck ); ?>">
766 + <tr key-ck-id="<?php echo esc_attr( $endcoded_ck ); ?>">
469 767 <td class="check-column">
470 768 <div class="ui checkbox">
471 - <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="" />
472 772 </div>
473 773 </td>
474 - <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>
475 - <td><?php echo ! empty( $pers_names ) ? implode( ', ', $pers_names ) : 'N/A'; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
476 - <td><?php echo esc_html( $desc ); ?></td>
477 - <td><code><?php echo esc_html( '...' . $ending ); // phpcs:ignore WordPress.Security.EscapeOutput ?></code></td>
478 - <td class="collapsing">
479 - <a class="ui green basic mini button" href="admin.php?page=AddApiKeys&editkey=<?php echo esc_html( $endcoded_ck ); ?>"><?php esc_html_e( 'Edit', 'mainwp' ); ?></a>
480 - <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>'; ?>
481 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; ?>
482 824 </tr>
483 - <?php
484 - }
485 - ?>
825 + <?php } ?>
486 826 </tbody>
487 827 </table>
488 - </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>
489 970 <script type="text/javascript">
490 971 var responsive = true;
491 - if( jQuery( window ).width() > 1140 ) {
972 + if (jQuery(window).width() > 1140) {
492 973 responsive = false;
493 974 }
494 - jQuery( document ).ready( function( $ ) {
975 + jQuery(document).ready(function ($) {
495 976 try {
496 - jQuery( '#mainwp-rest-api-keys-table' ).DataTable( {
497 - "lengthMenu": [ [5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, "All"] ],
498 - "stateSave" : true,
499 - "order" : [ [1, 'asc'] ],
500 - "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": [{
501 982 "targets": 'no-sort',
502 983 "orderable": false
503 - } ],
984 + }],
504 985 "responsive": responsive,
505 - "preDrawCallback": function() {
506 - jQuery( '#mainwp-rest-api-keys-table .ui.dropdown' ).dropdown();
507 - jQuery('#mainwp-rest-api-keys-table .ui.checkbox').checkbox();
508 - 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);
509 990 mainwp_table_check_columns_init(); // ajax: to fix checkbox all.
510 991 }
511 - } );
512 - mainwp_datatable_fix_menu_overflow();
513 - } catch(err) {
992 + });
993 + mainwp_datatable_fix_menu_overflow('#mainwp-rest-api-keys-v2-table', -70, 0);
994 + } catch {
514 995 // to fix js error.
515 996 }
516 997 });
517 998 </script>
@@ -516,38 +997,137 @@
516 997 });
517 998 </script>
518 999
519 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">
1026 + <?php
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
520 1089 static::render_footer();
521 1090 }
522 1091
523 -
524 1092 /**
525 1093 * Render table top.
1094 + *
1095 + * @param bool $has_no_api_keys Whether there are no API keys.
526 1096 */
527 - 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 + }
528 1101 ?>
529 - <div class="mainwp-actions-bar">
530 - <div class="ui grid">
531 - <div class="equal width row ui mini form">
532 - <div class="middle aligned column">
533 - <div id="mainwp-rest-api-bulk-actions-menu" class="ui selection dropdown">
534 - <div class="default text"><?php esc_html_e( 'Bulk actions', 'mainwp' ); ?></div>
535 - <i class="dropdown icon"></i>
536 - <div class="menu">
537 - <div class="item" data-value="delete"><?php esc_html_e( 'Delete', 'mainwp' ); ?></div>
1102 + <div class="mainwp-sub-header">
1103 + <div class="ui grid">
1104 + <div class="equal width row ui mini form">
1105 + <div class="middle aligned column">
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>
538 1113 </div>
539 - </div>
540 - <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; ?>
541 1117 </div>
542 - <div class="right aligned middle aligned column"></div>
1118 + <div class="right aligned middle aligned column">
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; ?>
1123 + </div>
1124 + </div>
543 1125 </div>
544 1126 </div>
545 - </div>
546 1127 <?php
547 1128 }
548 1129
549 -
550 1130 /**
551 1131 * Method check_rest_api_updates().
552 1132 *
553 1133 * To Checks for updating REST API keys.
@@ -582,158 +1162,272 @@
582 1162
583 1163
584 1164 /**
585 1165 * Method show_messages().
586 - *
587 - * Show actions messages.
588 1166 */
589 1167 public static function show_messages() {
590 - $msg = '';
591 - if ( isset( $_GET['message'] ) && ( 'saved' === $_GET['message'] || 'created' === $_GET['message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
592 - $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 + }
593 1178 }
594 1179 if ( ! empty( $msg ) ) {
595 1180 ?>
596 - <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>
597 1182 <?php
598 1183 }
599 1184 }
600 1185
601 - /** Render REST API SubPage */
602 - public static function render_rest_api_setings() {
603 - if ( ! mainwp_current_user_have_right( 'dashboard', 'manage_dashboard_restapi' ) ) {
604 - mainwp_do_not_have_permissions( esc_html__( 'manage dashboard REST API', 'mainwp' ) );
1186 + /**
1187 + * Render REST API SubPage
1188 + */
1189 + public static function render_rest_api_setings() { //phpcs:ignore -- NOSONAR - complex.
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 + );
1196 + return;
1197 + }
605 1198
606 - return;
1199 + $edit_key = false;
1200 + if ( isset( $_GET['editkey'] ) && ! empty( $_GET['editkey'] ) && isset( $_GET['_opennonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_opennonce'] ), 'mainwp-admin-nonce' ) ) {
1201 + $edit_key = wp_unslash( $_GET['editkey'] ); // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
607 1202 }
608 1203
609 - $edit_key = isset( $_GET['editkey'] ) && ! empty( $_GET['editkey'] ) ? urldecode( wp_unslash( $_GET['editkey'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
610 1204 $edit_item = array();
611 1205 if ( false !== $edit_key ) {
612 - $all_keys = get_option( 'mainwp_rest_api_keys', false );
613 - if ( ! is_array( $all_keys ) || ! isset( $all_keys[ $edit_key ] ) ) {
614 - $edit_key = false;
1206 + if ( empty( $_GET['rest_ver'] ) ) {
1207 + $edit_key = urldecode( $edit_key );
1208 + $all_keys = get_option( 'mainwp_rest_api_keys', false );
1209 + if ( ! is_array( $all_keys ) || ! isset( $all_keys[ $edit_key ] ) ) {
1210 + $edit_key = false;
1211 + } else {
1212 + $edit_item = $all_keys[ $edit_key ];
1213 + }
1214 +
1215 + if ( ! empty( $edit_item ) ) {
1216 + static::render_rest_api_edit( $edit_key, $edit_item );
1217 + return;
1218 + }
615 1219 } else {
616 - $edit_item = $all_keys[ $edit_key ];
1220 + $key_id = intval( $edit_key );
1221 + $edit_item = MainWP_DB::instance()->get_rest_api_key_by( $key_id );
1222 + if ( ! empty( $edit_item ) ) {
1223 + static::render_rest_api_v2_edit( $edit_item );
1224 + return;
1225 + }
617 1226 }
618 1227 }
619 1228
620 - if ( false !== $edit_key && ! empty( $edit_item ) ) {
621 - static::render_rest_api_edit( $edit_key, $edit_item );
622 - return;
623 - }
1229 + // we need to generate a consumer key and secret and return the result and save it into the database.
1230 + $_consumer_key = static::mainwp_generate_rand_hash();
1231 + $_consumer_secret = static::mainwp_generate_rand_hash();
624 1232
625 - // we need to generate a consumer key and secret and return the result and save it into the database.
626 - $consumer_key = 'ck_' . static::mainwp_generate_rand_hash();
627 - $consumer_secret = 'cs_' . static::mainwp_generate_rand_hash();
1233 + $consumer_key = 'ck_' . $_consumer_key;
1234 + $consumer_secret = 'cs_' . $_consumer_secret;
628 1235
629 1236 static::render_header( 'Settings' );
630 1237
631 1238 ?>
632 - <div id="rest-api-settings" class="ui segment">
633 - <?php if ( MainWP_Utility::show_mainwp_message( 'notice', 'mainwp-rest-api-info-message' ) ) : ?>
634 - <div class="ui info message">
635 - <i class="close icon mainwp-notice-dismiss" notice-id="mainwp-rest-api-info-message"></i>
636 - <?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>' ); ?>
1239 + <div id="rest-api-settings" class="ui padded segment">
1240 + <h2 class="ui dividing header">
1241 + <?php esc_html_e( 'MainWP REST API Credentials', 'mainwp' ); ?>
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' ); ?>
637 1244 </div>
638 - <?php endif; ?>
639 - <?php static::show_messages(); ?>
640 - <div id="api-credentials-created" class="ui green message"><?php esc_html_e( 'API credentials have been successfully generated. 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' ); ?></div>
641 - <div class="ui form">
642 - <form method="POST" action="">
643 - <?php wp_nonce_field( 'mainwp-admin-nonce' ); ?>
644 - <input type="hidden" name="wp_nonce" value="<?php echo esc_attr( wp_create_nonce( 'RESTAPI' ) ); ?>" />
645 - <?php
646 - /**
647 - * Action: rest_api_form_top
648 - *
649 - * Fires at the top of REST API form.
650 - *
651 - * @since 4.1
652 - */
653 - do_action( 'rest_api_form_top' );
654 - ?>
655 - <div class="ui grid field">
656 - <label class="six wide column middle aligned"><?php esc_html_e( 'Enable REST API key', 'mainwp' ); ?></label>
657 - <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">
658 - <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>
659 1322 </div>
660 - </div>
661 - <div class="ui grid field">
662 - <label class="six wide column middle aligned"><?php esc_html_e( 'Description', 'mainwp' ); ?></label>
663 - <div class="five wide column">
664 - <input type="text" name="mainwp_rest_add_api_key_desc" id="mainwp_rest_add_api_key_desc" value="" aria-label="<?php esc_attr_e( 'Key description', 'mainwp' ); ?>"/>
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>
665 1325 </div>
666 1326 </div>
667 - <div class="ui grid field">
668 - <label class="six wide column middle aligned"><?php esc_html_e( 'Consumer key', 'mainwp' ); ?></label>
1327 + </div>
669 1328
670 - <div class="five wide column">
671 - <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' ); ?>"/>
672 - </div>
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>
673 1346
674 - <div class="five wide column">
675 - <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' ); ?>">
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' ); ?>
676 1352 </div>
677 - </div>
1353 + </h3>
678 1354
679 1355 <div class="ui grid field">
680 - <label class="six wide column middle aligned"><?php esc_html_e( 'Consumer secret', 'mainwp' ); ?></label>
681 -
1356 + <label
1357 + class="six wide column middle aligned" for="mainwp_consumer_key"><?php esc_html_e( 'Consumer key', 'mainwp' ); ?></label>
682 1358 <div class="five wide column">
683 - <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' ); ?>"/>
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' ); ?>" />
684 1362 </div>
685 -
686 1363 <div class="five wide column">
687 - <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' ); ?>">
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' ); ?>">
688 1367 </div>
689 1368 </div>
690 - <?php $init_pers = 'r,w,d'; ?>
1369 +
691 1370 <div class="ui grid field">
692 - <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>
693 1373 <div class="five wide column">
694 - <div class="ui multiple selection dropdown" init-value="<?php echo esc_attr( $init_pers ); ?>">
695 - <input name="mainwp_rest_api_key_edit_pers" value="" type="hidden">
696 - <i class="dropdown icon"></i>
697 - <div class="default text"><?php echo ( '' === $init_pers ) ? esc_html__( 'No Permissions selected.', 'mainwp' ) : ''; ?></div>
698 - <div class="menu">
699 - <div class="item" data-value="r"><?php esc_html_e( 'Read', 'mainwp' ); ?></div>
700 - <div class="item" data-value="w"><?php esc_html_e( 'Write', 'mainwp' ); ?></div>
701 - <div class="item" data-value="d"><?php esc_html_e( 'Delete', 'mainwp' ); ?></div>
702 - </div>
703 - </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' ); ?>" />
704 1377 </div>
1378 + <div class="five wide column">
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' ); ?>">
1382 + </div>
705 1383 </div>
706 - <?php
707 - /**
708 - * Action: rest_api_form_bottom
709 - *
710 - * Fires at the bottom of REST API form.
711 - *
712 - * @since 4.1
713 - */
714 - do_action( 'rest_api_form_bottom' );
715 - ?>
716 - <div class="ui divider"></div>
717 - <input type="submit" name="submit" id="submit-save-settings-button" class="ui green big button" value="<?php esc_attr_e( 'Save Settings', 'mainwp' ); ?>"/>
718 - <div style="clear:both"></div>
719 - </form>
720 - </div>
1384 + </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>
721 1400 </div>
722 - <script type="text/javascript">
723 - jQuery(function($) {
724 - //we are going to inject the values into the copy buttons to make things easier for people
725 - $('#mainwp_consumer_key_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $consumer_key ); ?>');
726 - $('#mainwp_consumer_secret_clipboard_button').attr('data-clipboard-text', '<?php echo esc_html( $consumer_secret ); ?>');
727 - //initiate clipboard
728 - new ClipboardJS('.copy-to-clipboard');
729 - //show copy to clipboard buttons
730 - $('.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();
1421 +
1422 + $('#mainwp_rest_api_keys_compatible_v1').on('change', function () {
1423 + $('#mainwp-legacy-api-credentials').toggle();
731 1424 });
732 - </script>
1425 + });
1426 + </script>
733 1427 <?php
734 1428
735 - static::render_footer( 'Settings' );
1429 + static::render_footer();
736 1430 }
737 1431
738 1432 /**
739 1433 * Method mainwp_generate_rand_hash()
@@ -750,8 +1444,131 @@
750 1444 return bin2hex( openssl_random_pseudo_bytes( 20 ) ); // @codingStandardsIgnoreLine
751 1445 }
752 1446
753 1447 /**
1448 + * Method render_rest_api_v2_edit().
1449 + *
1450 + * Render REST API edit screen.
1451 + *
1452 + * @param mixed $item The Key edit.
1453 + */
1454 + public static function render_rest_api_v2_edit( $item ) { //phpcs:ignore -- NOSONAR - complex.
1455 + $keyid = $item->key_id;
1456 + $edit_desc = $item->description;
1457 + $enabled = $item->enabled ? true : false;
1458 + $ending = $item->truncated_key;
1459 + $perms = $item->permissions;
1460 +
1461 + $init_pers = '';
1462 + if ( 'read' === $perms ) {
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';
1468 + }
1469 + static::render_header( 'Edit' );
1470 + $el_id_res_api_1 = 'rest-api-settings';
1471 + ?>
1472 + <div id="<?php echo esc_attr( $el_id_res_api_1 ); ?>" class="ui segment">
1473 + <div class="ui form">
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">
1483 + <?php
1484 + if ( $item ) {
1485 + MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $enabled );
1486 + }
1487 + $el_id_en_res_api_1 = 'mainwp_enable_rest_api';
1488 + ?>
1489 + <?php esc_html_e( 'Enable REST API Key', 'mainwp' ); ?></label>
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' ); ?>" />
1496 + </div>
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">
1501 + <?php
1502 + if ( $item ) {
1503 + MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
1504 + }
1505 + ?>
1506 + <?php esc_html_e( 'Description', 'mainwp' ); ?></label>
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' ); ?>" />
1511 + </div>
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">
1516 + <?php
1517 + if ( $item ) {
1518 + MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
1519 + }
1520 + ?>
1521 + <?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></label>
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' ); ?>" />
1527 + </div>
1528 + </div>
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">
1533 + <?php
1534 + if ( $item ) {
1535 + $tmp = explode( ',', $init_pers );
1536 + $visible = 2 !== count( $tmp );
1537 + MainWP_Settings_Indicator::render_indicator( '', '', $visible );
1538 + }
1539 + ?>
1540 + <?php esc_html_e( 'Permissions', 'mainwp' ); ?></label>
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>
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>
1556 + </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>
1564 +
1565 + </div>
1566 + <?php
1567 + static::render_footer();
1568 + }
1569 +
1570 + /**
754 1571 * Method render_rest_api_edit().
755 1572 *
756 1573 * Render REST API edit screen.
757 1574 *
@@ -771,29 +1588,38 @@
771 1588 $init_pers = 'r,w,d'; // to compatible.
772 1589 }
773 1590
774 1591 static::render_header( 'Edit' );
1592 + $el_id_res_api_2 = 'rest-api-settings';
775 1593 ?>
776 - <div id="rest-api-settings" class="ui segment">
1594 + <div id="<?php echo esc_attr( $el_id_res_api_2 ); ?>" class="ui segment">
777 1595 <div class="ui form">
778 - <form method="POST" action="">
779 - <?php wp_nonce_field( 'mainwp-admin-nonce' ); ?>
780 - <input type="hidden" name="edit_key_nonce" value="<?php echo esc_attr( wp_create_nonce( 'edit-key-nonce-' . $keyid ) ); ?>" />
781 - <input type="hidden" name="editkey_id" value="<?php echo esc_html( $keyid ); ?>" />
782 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>">
783 - <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">
784 1604 <?php
785 1605 if ( $item ) {
786 1606 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $enabled );
787 1607 }
1608 + $el_id_en_res_api_2 = 'mainwp_enable_rest_api';
788 1609 ?>
789 - <?php esc_html_e( 'Enable REST API Key', 'mainwp' ); ?></label>
790 - <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">
791 - <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' ); ?>" />
792 - </div>
1610 + <?php esc_html_e( 'Enable API key', 'mainwp' ); ?></label>
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' ); ?>" />
793 1617 </div>
794 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"">
795 - <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">
796 1622 <?php
797 1623 if ( $item ) {
798 1624 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
799 1625 }
@@ -798,14 +1624,17 @@
798 1624 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
799 1625 }
800 1626 ?>
801 1627 <?php esc_html_e( 'Description', 'mainwp' ); ?></label>
802 - <div class="five wide column">
803 - <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' ); ?>"/>
804 - </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' ); ?>" />
805 1632 </div>
806 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>"">
807 - <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">
808 1637 <?php
809 1638 if ( $item ) {
810 1639 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
811 1640 }
@@ -810,16 +1639,19 @@
810 1639 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
811 1640 }
812 1641 ?>
813 1642 <?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></label>
814 - <div class="five wide column">
815 - <div class="ui disabled input">
816 - <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' ); ?>"/>
817 - </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' ); ?>" />
818 1648 </div>
819 1649 </div>
820 - <div class="ui grid field settings-field-indicator-wrapper <?php echo $item ? 'settings-field-indicator-edit-api-keys' : ''; ?>" default-indi-value="3">
821 - <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">
822 1654 <?php
823 1655 if ( $item ) {
824 1656 $tmp = explode( ',', $init_pers );
825 1657 $visible = 3 !== count( $tmp );
@@ -826,30 +1658,40 @@
826 1658 MainWP_Settings_Indicator::render_indicator( '', '', $visible );
827 1659 }
828 1660 ?>
829 1661 <?php esc_html_e( 'Permissions', 'mainwp' ); ?></label>
830 - <div class="five wide column">
831 - <div class="ui multiple selection dropdown" init-value="<?php echo esc_attr( $init_pers ); ?>">
832 - <input name="mainwp_rest_api_key_edit_pers" class="settings-field-value-change-handler" value="" type="hidden">
833 - <i class="dropdown icon"></i>
834 - <div class="default text"><?php echo ( '' === $init_pers ) ? esc_html__( 'No Permissions selected.', 'mainwp' ) : ''; ?></div>
835 - <div class="menu">
836 - <div class="item" data-value="r"><?php esc_html_e( 'Read', 'mainwp' ); ?></div>
837 - <div class="item" data-value="w"><?php esc_html_e( 'Write', 'mainwp' ); ?></div>
838 - <div class="item" data-value="d"><?php esc_html_e( 'Delete', 'mainwp' ); ?></div>
839 - </div>
840 - </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>
841 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>
842 1681 </div>
843 - <div class="ui divider"></div>
844 - <input type="submit" name="submit" id="submit" class="ui green big button" value="<?php esc_attr_e( 'Save API Key', 'mainwp' ); ?>"/>
845 - <div style="clear:both"></div>
846 - </form>
847 - </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>
848 1690
849 1691 </div>
850 1692 <?php
851 - static::render_footer( 'Edit' );
1693 + static::render_footer();
852 1694 }
853 1695
854 1696 /**
855 1697 * Method check_rest_api_enabled().
@@ -875,13 +1717,14 @@
875 1717 }
876 1718 }
877 1719
878 1720 $args = array(
879 - 'method' => 'GET',
880 - 'timeout' => 45,
881 - 'headers' => array(
1721 + 'method' => 'GET',
1722 + 'timeout' => 45,
1723 + 'headers' => array(
882 1724 'content-type' => 'application/json',
883 1725 ),
1726 + 'sslverify' => (bool) get_option( 'mainwp_sslVerifyCertificate', true ),
884 1727 );
885 1728
886 1729 if ( $check_logged_in && ! empty( $cookies ) ) {
887 1730 $args['cookies'] = $cookies;
@@ -886,9 +1729,9 @@
886 1729 if ( $check_logged_in && ! empty( $cookies ) ) {
887 1730 $args['cookies'] = $cookies;
888 1731 }
889 1732
890 - $site_url = get_option( 'siteurl' );
1733 + $site_url = get_option( 'home' );
891 1734 $response = wp_remote_post( $site_url . '/wp-json', $args );
892 1735 $body = wp_remote_retrieve_body( $response );
893 1736 $data = is_string( $body ) ? json_decode( $body, true ) : false;
894 1737
@@ -897,6 +1740,819 @@
897 1740 } elseif ( ! $check_logged_in ) {
898 1741 return static::check_rest_api_enabled( true );
899 1742 }
900 1743 return false;
1744 + }
1745 +
1746 + /**
1747 + * Method mainwp_help_content()
1748 + *
1749 + * Creates the MainWP Help Documentation List for the help component in the sidebar.
1750 + */
1751 + public static function mainwp_help_content() {
1752 + $allow_pages = array( 'RESTAPI', 'AddApiKeys' );
1753 + if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1754 + ?>
1755 + <p><?php esc_html_e( 'If you need help with the MainWP REST API, please review following help documents', 'mainwp' ); ?>
1756 + </p>
1757 + <div class="ui list">
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. ?>
1773 + <?php
1774 + /**
1775 + * Action: mainwp_rest_api_help_item
1776 + *
1777 + * Fires at the bottom of the help articles list in the Help sidebar on the REST API page.
1778 + *
1779 + * Suggested HTML markup:
1780 + *
1781 + * <div class="item"><a href="Your custom URL">Your custom text</a></div>
1782 + *
1783 + * @since 4.0
1784 + */
1785 + do_action( 'mainwp_rest_api_help_item' );
1786 + ?>
1787 + </div>
1788 + <?php
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' );
901 2557 }
902 2558 }