PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
mainwp / pages / page-mainwp-rest-api-page.php

page-mainwp-rest-api-page.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.2, at pages/page-mainwp-rest-api-page.php

2,559 lines 123.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP REST API page
4 *
5 * This Class handles building/Managing the
6 * REST API MainWP DashboardPage & all SubPages.
7 *
8 * @package MainWP/Dashboard
9 */
10
11 namespace MainWP\Dashboard;
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * Class MainWP_Rest_Api_Page
20 *
21 * @package MainWP\Dashboard
22 */
23 class MainWP_Rest_Api_Page { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.nore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.nore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.nore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
24
25 /**
26 * Protected static variable to hold the single instance of the class.
27 *
28 * @var mixed Default null
29 */
30 protected static $instance = null;
31
32 /**
33 * Public static varable to hold Subpages information.
34 *
35 * @var array $subPages
36 */
37 public static $subPages;
38
39 /**
40 * Private variable to hold the application passwords instance.
41 *
42 * @var MainWP_Application_Passwords
43 */
44 protected static $application_passwords;
45
46 /**
47 * Get Class Name
48 *
49 * @return __CLASS__
50 */
51 public static function get_class_name() {
52 return __CLASS__;
53 }
54
55 /**
56 * Return the single instance of the class.
57 *
58 * @return mixed $instance The single instance of the class.
59 */
60 public static function get_instance() {
61 if ( is_null( static::$instance ) ) {
62 static::$instance = new self();
63 }
64 return static::$instance;
65 }
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
76 /** Instantiate Hooks for the REST API Page. */
77 public static function init() {
78 /**
79 * Renders the REST API page header via the 'mainwp_pageheader_restapi' action.
80 *
81 * This hook is normally used in the same context of 'mainwp_getsubpages_restapi'
82 */
83 add_action( 'mainwp-pageheader-restapi', array( static::get_class_name(), 'render_header' ) );
84
85 /**
86 * Renders the REST API page footer via the 'mainwp-pagefooter-restapi' action.
87 *
88 * This hook is normally used in the same context of 'mainwp-getsubpages-restapi'
89 */
90 add_action( 'mainwp-pagefooter-restapi', array( static::get_class_name(), 'render_footer' ) );
91
92 add_action( 'mainwp_help_sidebar_content', array( static::get_class_name(), 'mainwp_help_content' ) );
93
94 add_action( 'admin_init', array( static::get_instance(), 'admin_init' ) );
95 }
96
97 /** Run the export_sites method that exports the Child Sites .csv file */
98 public function admin_init() {
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
131 $this->handle_rest_api_add_new();
132 $this->handle_rest_api_edit();
133 }
134
135
136 /**
137 * Instantiate the REST API Menu.
138 *
139 * @uses \MainWP\Dashboard\MainWP_Menu::is_disable_menu_item()
140 */
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
146 add_submenu_page(
147 'mainwp_tab',
148 $api_access_title,
149 ' <span id="mainwp-rest-api">' . $api_access_title . '</span>',
150 'read',
151 'RESTAPI',
152 array(
153 static::get_class_name(),
154 'render_all_api_keys',
155 )
156 );
157
158 if ( ! MainWP_Menu::is_disable_menu_item( 3, 'AddApiKeys' ) ) {
159 add_submenu_page(
160 'mainwp_tab',
161 $add_api_keys_title,
162 ' <div class="mainwp-hidden">' . $add_api_keys_title . '</div>',
163 'read',
164 'AddApiKeys',
165 array(
166 static::get_class_name(),
167 'render_rest_api_setings',
168 )
169 );
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 }
184
185 /**
186 * REST API Subpages
187 *
188 * Filters subpages for the REST API page.
189 *
190 * @since Unknown
191 */
192 static::$subPages = apply_filters( 'mainwp_getsubpages_restapi', array() );
193
194 if ( isset( static::$subPages ) && is_array( static::$subPages ) ) {
195 foreach ( static::$subPages as $subPage ) {
196 if ( MainWP_Menu::is_disable_menu_item( 3, 'RESTAPI' . $subPage['slug'] ) ) {
197 continue;
198 }
199 add_submenu_page( 'mainwp_tab', $subPage['title'], '<div class="mainwp-hidden">' . $subPage['title'] . '</div>', 'read', 'RESTAPI' . $subPage['slug'], $subPage['callback'] );
200 }
201 }
202
203 static::init_left_menu( static::$subPages );
204 }
205
206 /**
207 * Instantiate REST API SubPages Menu.
208 *
209 * @uses \MainWP\Dashboard\MainWP_Menu::is_disable_menu_item()
210 */
211 public static function init_subpages_menu() {
212 ?>
213 <div id="menu-mainwp-RESTAPI" class="mainwp-submenu-wrapper">
214 <div class="wp-submenu sub-open">
215 <div class="mainwp_boxout">
216 <div class="mainwp_boxoutin"></div>
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>
218 <?php
219 if ( isset( static::$subPages ) && is_array( static::$subPages ) && ! empty( static::$subPages ) ) {
220 foreach ( static::$subPages as $subPage ) {
221 if ( MainWP_Menu::is_disable_menu_item( 3, 'RESTAPI' . $subPage['slug'] ) ) {
222 continue;
223 }
224 ?>
225 <a href="<?php echo esc_url( admin_url( 'admin.php?page=RESTAPI' . $subPage['slug'] ) ); ?>" class="mainwp-submenu"><?php echo esc_html( $subPage['title'] ); ?></a>
226 <?php
227 }
228 }
229 ?>
230 </div>
231 </div>
232 </div>
233 <?php
234 }
235
236 /**
237 * Instantiate left menu
238 *
239 * REST API Page & SubPage link data.
240 *
241 * @param array $subPages SubPages Array.
242 */
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 }
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
261 MainWP_Menu::add_left_menu(
262 array(
263 'title' => esc_html__( 'Access', 'mainwp' ),
264 'parent_key' => 'mainwp_tab',
265 'slug' => 'RESTAPI',
266 'href' => $root_href,
267 'id' => 'mainwp-restapi-active-item',
268 'icon' => '<div class="mainwp-api-icon">API</div>',
269 ),
270 0
271 );
272
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',
289 'href' => 'admin.php?page=RESTAPI',
290 'slug' => 'RESTAPI',
291 'right' => '',
292 );
293
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',
322 'right' => '',
323 );
324 }
325
326 // Third-level items (actual links).
327 $init_sub_subleftmenu = array_merge(
328 $sub_rest_api,
329 $sub_application_passwords
330 );
331
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' );
334 foreach ( $init_sub_subleftmenu as $item ) {
335 if ( MainWP_Menu::is_disable_menu_item( 3, $item['slug'] ) ) {
336 continue;
337 }
338
339 MainWP_Menu::add_left_menu( $item, 2 );
340 }
341 }
342
343 /**
344 * Method handle_rest_api_add_new()
345 *
346 * Handle rest api settings
347 */
348 public function handle_rest_api_add_new() { // phpcs:ignore -- NOSONAR - complex.
349 // phpcs:disable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
350 $submit = false;
351
352 if ( isset( $_POST['submit'] ) && isset( $_GET['page'] ) && 'AddApiKeys' === $_GET['page'] ) {
353 $submit = true;
354 }
355
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 }
362 $all_keys = static::check_rest_api_updates();
363
364 if ( ! is_array( $all_keys ) ) {
365 $all_keys = array();
366 }
367
368 $consumer_key = isset( $_POST['mainwp_consumer_key'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_consumer_key'] ) ) : '';
369 $consumer_secret = isset( $_POST['mainwp_consumer_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_consumer_secret'] ) ) : '';
370 $desc = isset( $_POST['mainwp_rest_add_api_key_desc'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_add_api_key_desc'] ) ) : '';
371 $enabled = ! empty( $_POST['mainwp_enable_rest_api'] ) ? 1 : 0;
372 $pers = ! empty( $_POST['mainwp_rest_api_key_edit_pers'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_rest_api_key_edit_pers'] ) ) : '';
373
374 // hash the password.
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 )
430 );
431 exit();
432 }
433 // phpcs:enable
434 }
435
436 /**
437 * Method handle_rest_api_edit()
438 *
439 * Handle rest api settings
440 */
441 public function handle_rest_api_edit() { // phpcs:ignore -- NOSONAR - complex.
442
443 $edit_id = isset( $_POST['editkey_id'] ) ? sanitize_text_field( wp_unslash( $_POST['editkey_id'] ) ) : false;
444
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
452 $save = false;
453 $updated = false;
454 if ( ! empty( $edit_id ) ) {
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 );
465
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 }
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 }
501 }
502 }
503
504 $msg = '';
505 if ( $updated ) {
506 $msg = '&message=saved';
507 }
508
509 $this->invalidate_warm_cache();
510
511 wp_safe_redirect( admin_url( 'admin.php?page=RESTAPI' . $msg ) ); //phpcs:ignore -- ok.
512 exit();
513 }
514 }
515
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 /**
524 * Method ajax_rest_api_remove_keys()
525 *
526 * Remove API Key.
527 */
528 public function ajax_rest_api_remove_keys() { //phpcs:ignore -- NOSONAR - complex.
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 }
540 $ret = array( 'success' => false );
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
543
544 if ( ! empty( $cons_key_id ) ) {
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 }
556 }
557 if ( $save ) {
558 MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
559 }
560 }
561 $ret['success'] = 'SUCCESS';
562 $ret['result'] = esc_html__( 'REST API Key deleted successfully.', 'mainwp' );
563 $this->invalidate_warm_cache();
564 } else {
565 $ret['error'] = esc_html__( 'REST API Key ID empty.', 'mainwp' );
566 }
567 echo wp_json_encode( $ret );
568 exit;
569 }
570
571
572 /**
573 * Render Page Header.
574 *
575 * @param string $shownPage The page slug shown at this moment.
576 *
577 * @uses \MainWP\Dashboard\MainWP_Menu::is_disable_menu_item()
578 * @uses \MainWP\Dashboard\MainWP_UI::render_top_header()
579 * @uses \MainWP\Dashboard\MainWP_UI::render_page_navigation()
580 */
581 public static function render_header( $shownPage = '' ) { // phpcs:ignore -- NOSONAR - complex.
582
583 $params = array(
584 'title' => esc_html__( 'API Access', 'mainwp' ),
585 );
586
587 MainWP_UI::render_top_header( $params );
588
589 $renderItems = array();
590
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() ) {
593 $renderItems[] = array(
594 'title' => esc_html__( 'API Keys', 'mainwp' ),
595 'href' => 'admin.php?page=RESTAPI',
596 'active' => ( '' === $shownPage ) ? true : false,
597 );
598 }
599
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'] ) : '';
605 $renderItems[] = array(
606 'title' => esc_html__( 'Edit API Keys', 'mainwp' ),
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
608 'active' => ( 'Edit' === $shownPage ) ? true : false,
609 );
610 }
611 $renderItems[] = array(
612 'title' => esc_html__( 'Add API Keys', 'mainwp' ),
613 'href' => 'admin.php?page=AddApiKeys',
614 'active' => ( 'Settings' === $shownPage ) ? true : false,
615 );
616 }
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
630 if ( isset( static::$subPages ) && is_array( static::$subPages ) ) {
631 foreach ( static::$subPages as $subPage ) {
632 if ( MainWP_Menu::is_disable_menu_item( 3, 'RESTAPI' . $subPage['slug'] ) ) {
633 continue;
634 }
635 $item = array();
636 $item['title'] = $subPage['title'];
637 $item['href'] = 'admin.php?page=RESTAPI' . $subPage['slug'];
638 $item['active'] = ( $subPage['slug'] === $shownPage ) ? true : false;
639 $renderItems[] = $item;
640 }
641 }
642
643 MainWP_UI::render_page_navigation( $renderItems );
644 }
645
646 /**
647 * Close the HTML container.
648 */
649 public static function render_footer() {
650 echo '</div>';
651 }
652
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 }
697
698 /**
699 * Method render_api_keys_v1_table.
700 *
701 * @return void
702 */
703 public static function render_api_keys_v1_table() { //phpcs:ignore -- NOSONAR - complex.
704 $all_keys = static::check_rest_api_updates();
705 $can_edit_keys = static::can_edit_rest_api_keys();
706 $can_delete_keys = static::can_delete_rest_api_keys();
707
708 if ( ! is_array( $all_keys ) ) {
709 $all_keys = array();
710 }
711
712 if ( ! empty( $all_keys ) ) {
713 $write_delete = esc_html__( 'Write & Delete', 'mainwp' ); // NOSONAR - ok.
714 ?>
715
716 <table id="mainwp-rest-api-keys-table" class="ui unstackable single linetable">
717 <thead>
718 <tr>
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>
722 <th scope="col" class="collapsing"><?php esc_html_e( 'Status', 'mainwp' ); ?></th>
723 <th scope="col"><?php esc_html_e( 'API Key', 'mainwp' ); ?></th>
724 <th scope="col" class="collapsing"><?php esc_html_e( 'Permissions', 'mainwp' ); ?></th>
725 <th scope="col" class="no-sort collapsing"><?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></th>
726 <?php if ( $can_edit_keys || $can_delete_keys ) : ?>
727 <th scope="col" class="no-sort collapsing"></th>
728 <?php endif; ?>
729 </tr>
730 </thead>
731 <tbody id="mainwp-rest-api-body-table" class="mainwp-rest-api-body-table-manage">
732 <?php
733 foreach ( $all_keys as $ck => $item ) {
734 if ( ! is_array( $item ) ) {
735 continue;
736 }
737 $ending = substr( $ck, -8 );
738 $desc = isset( $item['desc'] ) && ! empty( $item['desc'] ) ? $item['desc'] : 'N/A';
739 $enabled = isset( $item['enabled'] ) && ! empty( $item['enabled'] ) ? true : false;
740 $endcoded_ck = rawurlencode( $ck );
741
742 $pers_codes = '';
743 if ( ! isset( $item['perms'] ) ) {
744 $pers_codes = 'r,w,d'; // to compatible.
745 } elseif ( ! empty( $item['perms'] ) ) {
746 $pers_codes = $item['perms'];
747 }
748
749 $pers_names = array();
750 if ( ! empty( $pers_codes ) && is_string( $pers_codes ) ) {
751 $pers_codes = explode( ',', $pers_codes );
752 if ( is_array( $pers_codes ) ) {
753 if ( in_array( 'r', $pers_codes ) ) {
754 $pers_names[] = esc_html__( 'Read', 'mainwp' );
755 }
756 if ( in_array( 'w', $pers_codes ) ) {
757 $pers_names[] = esc_html__( 'Write', 'mainwp' );
758 }
759 if ( in_array( 'd', $pers_codes ) ) {
760 $pers_names[] = esc_html__( 'Delete', 'mainwp' );
761 }
762 }
763 }
764
765 ?>
766 <tr key-ck-id="<?php echo esc_attr( $endcoded_ck ); ?>">
767 <td class="check-column">
768 <div class="ui checkbox">
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="" />
772 </div>
773 </td>
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>'; ?>
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; ?>
824 </tr>
825 <?php } ?>
826 </tbody>
827 </table>
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>
970 <script type="text/javascript">
971 var responsive = true;
972 if (jQuery(window).width() > 1140) {
973 responsive = false;
974 }
975 jQuery(document).ready(function ($) {
976 try {
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": [{
982 "targets": 'no-sort',
983 "orderable": false
984 }],
985 "responsive": responsive,
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);
990 mainwp_table_check_columns_init(); // ajax: to fix checkbox all.
991 }
992 });
993 mainwp_datatable_fix_menu_overflow('#mainwp-rest-api-keys-v2-table', -70, 0);
994 } catch {
995 // to fix js error.
996 }
997 });
998 </script>
999
1000 <?php
1001 }
1002
1003 /**
1004 * Render All API Keys
1005 */
1006 public static function render_all_api_keys() { // phpcs:ignore -- NOSONAR - complex.
1007 // Allow page if user can view REST API (any of manage/create/edit/delete).
1008 if ( ! static::can_access_rest_api() ) {
1009 static::render_permission_denied_ui(
1010 esc_html__( 'API Access', 'mainwp' ),
1011 esc_html__( 'You do not have permission to view the REST API. If you need access to this page please contact the dashboard administrator.', 'mainwp' )
1012 );
1013 return;
1014 }
1015 $all_keys = static::check_rest_api_updates();
1016 if ( ! is_array( $all_keys ) ) {
1017 $all_keys = array();
1018 }
1019 $all_keys_v2 = MainWP_DB::instance()->get_rest_api_keys();
1020 $has_no_api_keys = empty( $all_keys ) && empty( $all_keys_v2 );
1021 static::render_header();
1022 static::render_table_top( $has_no_api_keys );
1023 if ( ! static::check_rest_api_enabled() ) {
1024 ?>
1025 <div class="ui message yellow">
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
1089 static::render_footer();
1090 }
1091
1092 /**
1093 * Render table top.
1094 *
1095 * @param bool $has_no_api_keys Whether there are no API keys.
1096 */
1097 public static function render_table_top( $has_no_api_keys = false ) {
1098 if ( $has_no_api_keys ) {
1099 return;
1100 }
1101 ?>
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>
1113 </div>
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; ?>
1117 </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>
1125 </div>
1126 </div>
1127 <?php
1128 }
1129
1130 /**
1131 * Method check_rest_api_updates().
1132 *
1133 * To Checks for updating REST API keys.
1134 */
1135 public static function check_rest_api_updates() {
1136 $all_keys = get_option( 'mainwp_rest_api_keys', false );
1137 $cs = get_option( 'mainwp_rest_api_consumer_key', false );
1138
1139 // to compatible.
1140 if ( false === $all_keys || false !== $cs ) {
1141 if ( ! is_array( $all_keys ) ) {
1142 $all_keys = array();
1143 }
1144 if ( ! empty( $cs ) ) {
1145 $all_keys[ $cs ] = array(
1146 'ck' => get_option( 'mainwp_rest_api_consumer_key', '' ),
1147 'cs' => get_option( 'mainwp_rest_api_consumer_secret', '' ),
1148 'desc' => '',
1149 );
1150 }
1151 MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
1152
1153 if ( false !== $cs ) {
1154 delete_option( 'mainwp_rest_api_consumer_key' );
1155 delete_option( 'mainwp_rest_api_consumer_secret' );
1156 delete_option( 'mainwp_enable_rest_api' );
1157 }
1158 }
1159 // end.
1160 return $all_keys;
1161 }
1162
1163
1164 /**
1165 * Method show_messages().
1166 */
1167 public static function show_messages() {
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 }
1178 }
1179 if ( ! empty( $msg ) ) {
1180 ?>
1181 <div class="ui <?php echo esc_attr( $tone ); ?> message"><i class="close icon"></i><?php echo esc_html( $msg ); ?></div>
1182 <?php
1183 }
1184 }
1185
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 }
1198
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
1202 }
1203
1204 $edit_item = array();
1205 if ( false !== $edit_key ) {
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 }
1219 } else {
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 }
1226 }
1227 }
1228
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();
1232
1233 $consumer_key = 'ck_' . $_consumer_key;
1234 $consumer_secret = 'cs_' . $_consumer_secret;
1235
1236 static::render_header( 'Settings' );
1237
1238 ?>
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' ); ?>
1244 </div>
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>
1322 </div>
1323 <div class="mainwp-permission-chips-help">
1324 <span class="ui small grey text"><?php esc_html_e( 'Choose what actions this key can perform on your Dashboard data.', 'mainwp' ); ?></span>
1325 </div>
1326 </div>
1327 </div>
1328
1329 <?php
1330 $token = $_consumer_secret . '==' . $_consumer_key;
1331 ?>
1332 <div class="ui grid field">
1333 <label
1334 class="six wide column middle aligned" for="mainwp_rest_token"><?php esc_html_e( 'API key (Bearer token)', 'mainwp' ); ?></label>
1335 <div class="five wide column">
1336 <input type="text" name="mainwp_rest_token" id="mainwp_rest_token"
1337 value="<?php echo esc_html( $token ); ?>" readonly
1338 aria-label="<?php esc_attr_e( 'API Token', 'mainwp' ); ?>" />
1339 </div>
1340 <div class="five wide column">
1341 <input id="mainwp_rest_token_clipboard_button" style="display:nonce;" type="button" name=""
1342 class="ui green basic button copy-to-clipboard"
1343 value="<?php esc_attr_e( 'Copy to Clipboard', 'mainwp' ); // NOSONAR. ?>">
1344 </div>
1345 </div>
1346
1347 <div id="mainwp-legacy-api-credentials" class="ui segment" style="display:none">
1348 <h3 class="ui dividing header">
1349 <?php esc_html_e( 'Legacy API Credentials', 'mainwp' ); ?>
1350 <div class="sub header">
1351 <?php esc_html_e( 'Authentication compatible only with the MainWP REST API version 1!', 'mainwp' ); ?>
1352 </div>
1353 </h3>
1354
1355 <div class="ui grid field">
1356 <label
1357 class="six wide column middle aligned" for="mainwp_consumer_key"><?php esc_html_e( 'Consumer key', 'mainwp' ); ?></label>
1358 <div class="five wide column">
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' ); ?>" />
1362 </div>
1363 <div class="five wide column">
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' ); ?>">
1367 </div>
1368 </div>
1369
1370 <div class="ui grid field">
1371 <label
1372 class="six wide column middle aligned" for="mainwp_consumer_secret"><?php esc_html_e( 'Consumer secret', 'mainwp' ); ?></label>
1373 <div class="five wide column">
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' ); ?>" />
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>
1383 </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>
1400 </div>
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();
1424 });
1425 });
1426 </script>
1427 <?php
1428
1429 static::render_footer();
1430 }
1431
1432 /**
1433 * Method mainwp_generate_rand_hash()
1434 *
1435 * Generates a random hash to be used when generating the consumer key and secret.
1436 *
1437 * @return string Returns random string.
1438 */
1439 public static function mainwp_generate_rand_hash() {
1440 if ( ! function_exists( 'openssl_random_pseudo_bytes' ) ) {
1441 return sha1( wp_rand() ); // NOSONAR - safe for keys.
1442 }
1443
1444 return bin2hex( openssl_random_pseudo_bytes( 20 ) ); // @codingStandardsIgnoreLine
1445 }
1446
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 /**
1571 * Method render_rest_api_edit().
1572 *
1573 * Render REST API edit screen.
1574 *
1575 * @param string $keyid Key ID edit.
1576 * @param array $item The Key edit.
1577 */
1578 public static function render_rest_api_edit( $keyid, $item ) { //phpcs:ignore -- NOSONAR - complex.
1579
1580 $edit_desc = is_array( $item ) && isset( $item['desc'] ) ? $item['desc'] : '';
1581 $enabled = is_array( $item ) && isset( $item['enabled'] ) && ! empty( $item['enabled'] ) ? true : false;
1582 $ending = substr( $keyid, -8 );
1583
1584 $init_pers = '';
1585 if ( isset( $item['perms'] ) ) {
1586 $init_pers = $item['perms'];
1587 } else {
1588 $init_pers = 'r,w,d'; // to compatible.
1589 }
1590
1591 static::render_header( 'Edit' );
1592 $el_id_res_api_2 = 'rest-api-settings';
1593 ?>
1594 <div id="<?php echo esc_attr( $el_id_res_api_2 ); ?>" class="ui segment">
1595 <div class="ui form">
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">
1604 <?php
1605 if ( $item ) {
1606 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $enabled );
1607 }
1608 $el_id_en_res_api_2 = 'mainwp_enable_rest_api';
1609 ?>
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' ); ?>" />
1617 </div>
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">
1622 <?php
1623 if ( $item ) {
1624 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $edit_desc );
1625 }
1626 ?>
1627 <?php esc_html_e( 'Description', 'mainwp' ); ?></label>
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' ); ?>" />
1632 </div>
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">
1637 <?php
1638 if ( $item ) {
1639 MainWP_Settings_Indicator::render_not_default_indicator( 'none_preset_value', $ending );
1640 }
1641 ?>
1642 <?php esc_html_e( 'Consumer key ending in', 'mainwp' ); ?></label>
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' ); ?>" />
1648 </div>
1649 </div>
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">
1654 <?php
1655 if ( $item ) {
1656 $tmp = explode( ',', $init_pers );
1657 $visible = 3 !== count( $tmp );
1658 MainWP_Settings_Indicator::render_indicator( '', '', $visible );
1659 }
1660 ?>
1661 <?php esc_html_e( 'Permissions', 'mainwp' ); ?></label>
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>
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>
1681 </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>
1690
1691 </div>
1692 <?php
1693 static::render_footer();
1694 }
1695
1696 /**
1697 * Method check_rest_api_enabled().
1698 *
1699 * @param bool $check_logged_in check for logged in user or not.
1700 *
1701 * @return bool check result.
1702 */
1703 public static function check_rest_api_enabled( $check_logged_in = false ) { // phpcs:ignore -- NOSONAR - complex.
1704 $cookies = array();
1705 if ( $check_logged_in && is_user_logged_in() && defined( 'LOGGED_IN_COOKIE' ) ) {
1706 $cookies = array();
1707 $auth_cookies = wp_parse_auth_cookie( $_COOKIE[ LOGGED_IN_COOKIE ], 'logged_in' ); // phpcs:ignore -- ok.
1708 if ( is_array( $auth_cookies ) ) {
1709 foreach ( $auth_cookies as $name => $value ) {
1710 $cookies[] = new \WP_Http_Cookie(
1711 array(
1712 'name' => $name,
1713 'value' => $value,
1714 )
1715 );
1716 }
1717 }
1718 }
1719
1720 $args = array(
1721 'method' => 'GET',
1722 'timeout' => 45,
1723 'headers' => array(
1724 'content-type' => 'application/json',
1725 ),
1726 'sslverify' => (bool) get_option( 'mainwp_sslVerifyCertificate', true ),
1727 );
1728
1729 if ( $check_logged_in && ! empty( $cookies ) ) {
1730 $args['cookies'] = $cookies;
1731 }
1732
1733 $site_url = get_option( 'home' );
1734 $response = wp_remote_post( $site_url . '/wp-json', $args );
1735 $body = wp_remote_retrieve_body( $response );
1736 $data = is_string( $body ) ? json_decode( $body, true ) : false;
1737
1738 if ( is_array( $data ) & isset( $data['routes'] ) && ! empty( $data['routes'] ) ) {
1739 return true;
1740 } elseif ( ! $check_logged_in ) {
1741 return static::check_rest_api_enabled( true );
1742 }
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' );
2557 }
2558 }
2559