PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.23
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.23
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / admin / class-metasync-dev-panel.php

class-metasync-dev-panel.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.23, at admin/class-metasync-dev-panel.php

613 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // If this file is called directly, abort.
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Developer Panel for MetaSync Plugin
9 *
10 * Provides a password-protected panel for switching between production and staging endpoints.
11 *
12 * @package Metasync
13 * @subpackage Metasync/admin
14 */
15
16 /**
17 * The developer panel class.
18 *
19 * Manages the developer tools panel with endpoint switching capabilities.
20 *
21 * @package Metasync
22 * @subpackage Metasync/admin
23 */
24 class Metasync_Dev_Panel {
25
26 /**
27 * The ID of this plugin.
28 *
29 * @var string
30 */
31 private $plugin_name;
32
33 /**
34 * The version of this plugin.
35 *
36 * @var string
37 */
38 private $version;
39
40 /**
41 * Option name for storing the developer panel password.
42 */
43 const PASSWORD_OPTION = 'metasync_dev_panel_password';
44
45 /**
46 * Authentication manager instance.
47 *
48 * @var Metasync_Auth_Manager
49 */
50 private $auth;
51
52 /**
53 * Initialize the class and set its properties.
54 *
55 * @param string $plugin_name The name of this plugin.
56 * @param string $version The version of this plugin.
57 */
58 public function __construct( $plugin_name, $version ) {
59 $this->plugin_name = $plugin_name;
60 $this->version = $version;
61
62 // Initialize auth manager with 30-minute timeout
63 if ( class_exists( 'Metasync_Auth_Manager' ) ) {
64 $this->auth = new Metasync_Auth_Manager( 'dev_panel', 1800 );
65 }
66
67 // Register hooks
68 add_action( 'admin_menu', array( $this, 'add_dev_panel_menu' ) );
69 add_action( 'admin_notices', array( $this, 'display_staging_mode_banner' ) );
70 add_action( 'wp_ajax_metasync_switch_endpoints', array( $this, 'ajax_switch_endpoints' ) );
71 add_action( 'wp_ajax_metasync_update_dev_password', array( $this, 'ajax_update_dev_password' ) );
72 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
73 }
74
75 /**
76 * Register the developer panel menu (hidden from menu, accessible via URL).
77 */
78 public function add_dev_panel_menu() {
79 // Add hidden submenu page (no parent = hidden from menu)
80 // Accessible to admins only (developer tools)
81 add_submenu_page(
82 null,
83 'Developer Tools',
84 'Developer Tools',
85 'manage_options',
86 Metasync_Admin::$page_slug . '-dev-tools',
87 array( $this, 'render_dev_panel_page' )
88 );
89 }
90
91 /**
92 * Display staging mode banner on all admin pages.
93 */
94 public function display_staging_mode_banner() {
95 // Only show if staging mode is active
96 if ( ! Metasync_Endpoint_Manager::is_staging_mode() ) {
97 return;
98 }
99
100 // Only show in admin area
101 if ( ! is_admin() ) {
102 return;
103 }
104
105 // Don't show on dev panel page itself
106 $current_page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : '';
107 if ( $current_page === Metasync_Admin::$page_slug . '-dev-tools' ) {
108 return;
109 }
110
111 $dev_panel_url = admin_url( 'admin.php?page=' . Metasync_Admin::$page_slug . '-dev-tools' );
112 ?>
113 <div class="notice notice-warning" style="border-left: 4px solid #ff9800; background: #fff3cd; padding: 12px 15px;">
114 <p style="font-size: 14px; margin: 0;">
115 <strong>STAGING MODE ACTIVE</strong><br>
116 All <?php echo esc_html( Metasync::get_effective_plugin_name() ); ?> API endpoints are currently pointing to STAGING servers. This is intended for development and testing only.
117 <br>
118 <a href="<?php echo esc_url( $dev_panel_url ); ?>" class="button button-secondary" style="margin-top: 8px;">
119 <span class="dashicons dashicons-admin-tools" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Open Developer Tools
120 </a>
121 </p>
122 </div>
123 <?php
124 }
125
126 /**
127 * Enqueue scripts and styles for dev panel.
128 *
129 * @param string $hook The current admin page.
130 */
131 public function enqueue_scripts( $hook ) {
132 // Only load on dev panel page
133 if ( isset( $_GET['page'] ) && $_GET['page'] === Metasync_Admin::$page_slug . '-dev-tools' ) {
134 wp_enqueue_style( 'metasync-admin' );
135 }
136 }
137
138 /**
139 * Render the developer panel page.
140 */
141 public function render_dev_panel_page() {
142 // Check permissions (admin only for developer tools)
143 if ( ! current_user_can( 'manage_options' ) ) {
144 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'metasync' ) );
145 }
146
147 // Check if password is set
148 $saved_password = get_option( self::PASSWORD_OPTION, '' );
149
150 // If no password is set, show password setup form
151 if ( empty( $saved_password ) ) {
152 $this->render_password_setup_form();
153 return;
154 }
155
156 // Check authentication
157 if ( ! $this->auth || ! $this->auth->has_access() ) {
158 $this->render_password_form();
159 return;
160 }
161
162 // User is authenticated - show endpoint switcher
163 $this->render_endpoint_switcher();
164 }
165
166 /**
167 * Render the password setup form (first-time setup).
168 */
169 private function render_password_setup_form() {
170 $setup_error = '';
171
172 // Handle password setup submission
173 if ( isset( $_POST['dev_panel_setup_submit'] ) ) {
174 if ( wp_verify_nonce( $_POST['dev_panel_setup_nonce'], 'metasync_dev_panel_setup' ) ) {
175 $new_password = sanitize_text_field( $_POST['dev_panel_new_password'] );
176 $confirm_password = sanitize_text_field( $_POST['dev_panel_confirm_password'] );
177
178 if ( empty( $new_password ) ) {
179 $setup_error = 'Password cannot be empty.';
180 } elseif ( $new_password !== $confirm_password ) {
181 $setup_error = 'Passwords do not match.';
182 } elseif ( strlen( $new_password ) < 6 ) {
183 $setup_error = 'Password must be at least 6 characters long.';
184 } else {
185 $result = update_option( self::PASSWORD_OPTION, $new_password );
186 if ( $result ) {
187 // Grant access immediately
188 if ( $this->auth ) {
189 $this->auth->grant_transient_access();
190 }
191 // Refresh to show authenticated state - add timestamp to prevent caching
192 wp_safe_redirect( add_query_arg( 'setup', 'complete', $_SERVER['REQUEST_URI'] ) );
193 exit;
194 } else {
195 $setup_error = 'Failed to save password. Please try again.';
196 }
197 }
198 }
199 }
200 ?>
201 <div class="wrap metasync-dashboard-wrap">
202 <?php Metasync_Admin::render_static_header( 'Developer Tools - Setup' ); ?>
203
204 <div class="dashboard-card" style="max-width: 600px; margin: 0 auto;">
205 <h2 style="text-align: center; color: #fff;">Developer Tools - Initial Setup</h2>
206 <p style="color: #646970; margin-bottom: 30px; text-align: center;">
207 Welcome to the Developer Tools panel. Please create a password to secure access to endpoint switching features.
208 </p>
209
210 <?php if ( ! empty( $setup_error ) ) : ?>
211 <div style="background: #f8d7da; color: #721c24; padding: 12px; border-radius: 6px; margin-bottom: 20px; border: 1px solid #f5c6cb;">
212 <strong>Error:</strong> <?php echo esc_html( $setup_error ); ?>
213 </div>
214 <?php endif; ?>
215
216 <form method="post" action="" style="max-width: 400px; margin: 0 auto;">
217 <?php wp_nonce_field( 'metasync_dev_panel_setup', 'dev_panel_setup_nonce' ); ?>
218
219 <div style="margin-bottom: 20px;">
220 <label for="dev_panel_new_password" style="display: block; font-weight: 600; margin-bottom: 8px; color: #fff;">
221 Create Password
222 </label>
223 <input
224 type="password"
225 id="dev_panel_new_password"
226 name="dev_panel_new_password"
227 placeholder="Enter a strong password (min 6 characters)"
228 style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box;"
229 required
230 autocomplete="off"
231 />
232 </div>
233
234 <div style="margin-bottom: 20px;">
235 <label for="dev_panel_confirm_password" style="display: block; font-weight: 600; margin-bottom: 8px; color: #fff;">
236 Confirm Password
237 </label>
238 <input
239 type="password"
240 id="dev_panel_confirm_password"
241 name="dev_panel_confirm_password"
242 placeholder="Re-enter your password"
243 style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box;"
244 required
245 autocomplete="off"
246 />
247 </div>
248
249 <div style="text-align: center;">
250 <button
251 type="submit"
252 name="dev_panel_setup_submit"
253 value="1"
254 class="button button-primary"
255 style="padding: 12px 24px; font-size: 14px; font-weight: 600;"
256 >
257 Create Password & Continue
258 </button>
259 </div>
260 </form>
261 </div>
262 </div>
263
264 <script>
265 jQuery(document).ready(function($) {
266 $('#dev_panel_new_password').focus();
267 });
268 </script>
269 <?php
270 }
271
272 /**
273 * Render the password authentication form.
274 */
275 private function render_password_form() {
276 $password_error = '';
277
278 // Handle password submission
279 if ( isset( $_POST['dev_panel_password_submit'] ) ) {
280 if ( wp_verify_nonce( $_POST['dev_panel_nonce'], 'metasync_dev_panel_nonce' ) ) {
281 $submitted_password = sanitize_text_field( $_POST['dev_panel_password'] );
282 $saved_password = get_option( self::PASSWORD_OPTION, '' );
283
284 if ( $this->auth && $this->auth->verify_and_grant( $submitted_password, $saved_password, false ) ) {
285 // Refresh page to show authenticated state - add timestamp to prevent caching
286 wp_safe_redirect( add_query_arg( 'login', 'success', $_SERVER['REQUEST_URI'] ) );
287 exit;
288 } else {
289 $password_error = 'Incorrect password. Please try again.';
290 }
291 }
292 }
293 ?>
294 <div class="wrap metasync-dashboard-wrap">
295 <?php Metasync_Admin::render_static_header( 'Developer Tools' ); ?>
296
297 <div class="dashboard-card" style="max-width: 500px; margin: 0 auto;">
298 <h2 style="text-align: center; color: #fff;">Protected Area</h2>
299 <p style="color: #646970; margin-bottom: 30px; text-align: center;">
300 Please enter the password to access the Developer Tools panel.
301 </p>
302
303 <?php if ( ! empty( $password_error ) ) : ?>
304 <div style="background: #f8d7da; color: #721c24; padding: 12px; border-radius: 6px; margin-bottom: 20px; border: 1px solid #f5c6cb;">
305 <strong>Access Denied:</strong> <?php echo esc_html( $password_error ); ?>
306 </div>
307 <?php endif; ?>
308
309 <form method="post" action="" style="max-width: 400px; margin: 0 auto;">
310 <?php wp_nonce_field( 'metasync_dev_panel_nonce', 'dev_panel_nonce' ); ?>
311
312 <div style="margin-bottom: 20px;">
313 <label for="dev_panel_password" style="display: block; font-weight: 600; margin-bottom: 8px; color: #fff;">
314 Enter Password
315 </label>
316 <input
317 type="password"
318 id="dev_panel_password"
319 name="dev_panel_password"
320 placeholder="Enter developer panel password"
321 style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box;"
322 required
323 autocomplete="off"
324 />
325 </div>
326
327 <div style="text-align: center;">
328 <button
329 type="submit"
330 name="dev_panel_password_submit"
331 value="1"
332 class="button button-primary"
333 style="padding: 12px 24px; font-size: 14px; font-weight: 600;"
334 >
335 Submit Password
336 </button>
337 </div>
338 </form>
339 </div>
340 </div>
341
342 <script>
343 jQuery(document).ready(function($) {
344 $('#dev_panel_password').focus();
345 });
346 </script>
347 <?php
348 }
349
350 /**
351 * Render the endpoint switcher interface.
352 */
353 private function render_endpoint_switcher() {
354 $current_mode = Metasync_Endpoint_Manager::get_mode();
355 $all_endpoints = Metasync_Endpoint_Manager::get_all_endpoints();
356 $message = '';
357 $message_type = '';
358
359 // Handle mode switch
360 if ( isset( $_POST['switch_endpoints_submit'] ) ) {
361 if ( wp_verify_nonce( $_POST['switch_endpoints_nonce'], 'metasync_switch_endpoints' ) ) {
362 $new_mode = sanitize_text_field( $_POST['endpoint_mode'] );
363 $result = Metasync_Endpoint_Manager::set_mode( $new_mode );
364
365 if ( $result ) {
366 $message = 'Successfully switched to ' . esc_html( $new_mode ) . ' mode!';
367 $message_type = 'success';
368 $current_mode = $new_mode;
369 $all_endpoints = Metasync_Endpoint_Manager::get_all_endpoints();
370 } else {
371 $message = 'Failed to switch endpoints.';
372 $message_type = 'error';
373 }
374 }
375 }
376
377 // Handle password update
378 if ( isset( $_POST['update_password_submit'] ) ) {
379 if ( wp_verify_nonce( $_POST['update_password_nonce'], 'metasync_update_dev_password' ) ) {
380 $new_password = sanitize_text_field( $_POST['dev_panel_new_password'] );
381
382 if ( empty( $new_password ) ) {
383 $message = 'Password cannot be empty.';
384 $message_type = 'error';
385 } elseif ( strlen( $new_password ) < 6 ) {
386 $message = 'Password must be at least 6 characters long.';
387 $message_type = 'error';
388 } else {
389 $result = update_option( self::PASSWORD_OPTION, $new_password );
390 if ( $result ) {
391 $message = 'Password updated successfully!';
392 $message_type = 'success';
393 } else {
394 $message = 'Failed to update password.';
395 $message_type = 'error';
396 }
397 }
398 }
399 }
400
401 // Handle logout
402 if ( isset( $_POST['logout_submit'] ) ) {
403 if ( wp_verify_nonce( $_POST['logout_nonce'], 'metasync_dev_panel_logout' ) ) {
404 if ( $this->auth ) {
405 $this->auth->revoke_access();
406 }
407 wp_safe_redirect( remove_query_arg( array( 'setup', 'login' ), $_SERVER['REQUEST_URI'] ) );
408 exit;
409 }
410 }
411 ?>
412 <div class="wrap metasync-dashboard-wrap">
413 <?php Metasync_Admin::render_static_header( 'Developer Tools' ); ?>
414
415 <?php if ( ! empty( $message ) ) : ?>
416 <div class="notice notice-<?php echo esc_attr( $message_type ); ?>" style="margin: 15px 0; padding: 12px;">
417 <p><?php echo esc_html( $message ); ?></p>
418 </div>
419 <?php endif; ?>
420
421 <div class="dashboard-card">
422 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
423 <div>
424 <h2 style="margin: 0; color: #fff;">Developer Tools - Endpoint Switching</h2>
425 <p style="color: #646970; margin: 5px 0 0 0;">
426 Switch between production and staging API endpoints for testing.
427 </p>
428 </div>
429 <form method="post" style="margin: 0;">
430 <?php wp_nonce_field( 'metasync_dev_panel_logout', 'logout_nonce' ); ?>
431 <button type="submit" name="logout_submit" value="1" class="button" style="margin: 0;">
432 <span class="dashicons dashicons-exit" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Logout
433 </button>
434 </form>
435 </div>
436
437 <div style="background: <?php echo $current_mode === 'staging' ? '#fff3cd' : '#d1ecf1'; ?>; border-left: 4px solid <?php echo $current_mode === 'staging' ? '#ff9800' : '#0c5460'; ?>; padding: 15px; border-radius: 6px; margin-bottom: 30px;">
438 <h3 style="margin: 0 0 5px 0; font-size: 16px; color: #000;">
439 Current Mode: <span style="color: <?php echo $current_mode === 'staging' ? '#ff9800' : '#0c5460'; ?>; font-weight: 700;">
440 <?php echo esc_html( strtoupper( $current_mode ) ); ?>
441 </span>
442 </h3>
443 <p style="margin: 0; font-size: 13px; color: #3c434a;">
444 <?php
445 if ( $current_mode === 'staging' ) {
446 echo 'All API calls are pointing to staging servers. Remember to switch back to production when done testing!';
447 } else {
448 echo 'All API calls are pointing to production servers (live data).';
449 }
450 ?>
451 </p>
452 </div>
453
454 <h3 style="margin-top: 30px; margin-bottom: 15px; color: #fff;">Switch Endpoint Mode</h3>
455 <form method="post">
456 <?php wp_nonce_field( 'metasync_switch_endpoints', 'switch_endpoints_nonce' ); ?>
457
458 <div style="margin-bottom: 20px;">
459 <label style="display: block; margin-bottom: 10px; padding: 15px; border: 2px solid <?php echo $current_mode === 'production' ? '#2271b1' : '#ddd'; ?>; border-radius: 6px; cursor: pointer; background: <?php echo $current_mode === 'production' ? '#f0f6fc' : '#fff'; ?>; color: #1d2327;">
460 <input type="radio" name="endpoint_mode" value="production" <?php checked( $current_mode, 'production' ); ?> style="margin-right: 10px;">
461 <strong style="color: #1d2327;">Production</strong> - Live <?php echo esc_html( Metasync::get_effective_plugin_name() ); ?> servers (api.searchatlas.com)
462 </label>
463
464 <label style="display: block; margin-bottom: 10px; padding: 15px; border: 2px solid <?php echo $current_mode === 'staging' ? '#d63638' : '#ddd'; ?>; border-radius: 6px; cursor: pointer; background: <?php echo $current_mode === 'staging' ? '#fcf0f1' : '#fff'; ?>; color: #1d2327;">
465 <input type="radio" name="endpoint_mode" value="staging" <?php checked( $current_mode, 'staging' ); ?> style="margin-right: 10px;">
466 <strong style="color: #1d2327;">Staging</strong> - Testing servers (api.staging.searchatlas.com) - For development only
467 </label>
468 </div>
469
470 <button type="submit" name="switch_endpoints_submit" value="1" class="button button-primary">
471 <span class="dashicons dashicons-controls-repeat"></span> Switch Endpoints
472 </button>
473 </form>
474 </div>
475
476 <div class="dashboard-card">
477 <h3 style="color: #fff;">Current Endpoint Configuration</h3>
478 <table class="widefat" style="border: 1px solid #ddd;">
479 <thead>
480 <tr>
481 <th style="padding: 12px; background: #f6f7f7; color: #1d2327;">Endpoint Type</th>
482 <th style="padding: 12px; background: #f6f7f7; color: #1d2327;">Current URL</th>
483 </tr>
484 </thead>
485 <tbody>
486 <?php foreach ( $all_endpoints as $key => $url ) : ?>
487 <tr>
488 <td style="padding: 12px; border-top: 1px solid #ddd; color: #1d2327;"><code><?php echo esc_html( $key ); ?></code></td>
489 <td style="padding: 12px; border-top: 1px solid #ddd; font-family: monospace; font-size: 13px; color: #1d2327;">
490 <?php echo esc_html( $url ); ?>
491 </td>
492 </tr>
493 <?php endforeach; ?>
494 </tbody>
495 </table>
496 </div>
497
498 <div class="dashboard-card">
499 <h3 style="color: #fff;">Password Management</h3>
500 <p style="color: #646970; margin-bottom: 20px;">
501 Update the password required to access this developer panel.
502 </p>
503
504 <form method="post" style="max-width: 500px;">
505 <?php wp_nonce_field( 'metasync_update_dev_password', 'update_password_nonce' ); ?>
506
507 <div style="margin-bottom: 15px;">
508 <label for="dev_panel_new_password" style="display: block; font-weight: 600; margin-bottom: 8px; color: #fff;">
509 New Password (min 6 characters)
510 </label>
511 <input
512 type="password"
513 id="dev_panel_new_password"
514 name="dev_panel_new_password"
515 placeholder="Enter new password"
516 class="regular-text"
517 style="padding: 8px;"
518 />
519 </div>
520
521 <button type="submit" name="update_password_submit" value="1" class="button">
522 Update Password
523 </button>
524 </form>
525 </div>
526 </div>
527 <?php
528 }
529
530 /**
531 * AJAX handler for switching endpoints.
532 */
533 public function ajax_switch_endpoints() {
534 // Verify nonce
535 check_ajax_referer( 'metasync_switch_endpoints', 'nonce' );
536
537 // Verify permissions (matches main plugin's access control)
538 if ( ! Metasync::current_user_has_plugin_access() ) {
539 wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
540 return;
541 }
542
543 // Verify authentication
544 if ( ! $this->auth || ! $this->auth->has_access() ) {
545 wp_send_json_error( array( 'message' => 'Authentication required' ) );
546 return;
547 }
548
549 // Get and validate mode
550 $mode = isset( $_POST['endpoint_mode'] ) ? sanitize_text_field( $_POST['endpoint_mode'] ) : '';
551 if ( ! in_array( $mode, array( 'production', 'staging' ), true ) ) {
552 wp_send_json_error( array( 'message' => 'Invalid mode' ) );
553 return;
554 }
555
556 // Switch mode
557 $result = Metasync_Endpoint_Manager::set_mode( $mode );
558
559 if ( $result ) {
560 wp_send_json_success(
561 array(
562 'message' => "Successfully switched to {$mode} mode",
563 'mode' => $mode,
564 'endpoints' => Metasync_Endpoint_Manager::get_all_endpoints(),
565 )
566 );
567 } else {
568 wp_send_json_error( array( 'message' => 'Failed to switch endpoints' ) );
569 }
570 }
571
572 /**
573 * AJAX handler for updating dev panel password.
574 */
575 public function ajax_update_dev_password() {
576 // Verify nonce
577 check_ajax_referer( 'metasync_update_dev_password', 'nonce' );
578
579 // Verify permissions (matches main plugin's access control)
580 if ( ! Metasync::current_user_has_plugin_access() ) {
581 wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
582 return;
583 }
584
585 // Verify authentication
586 if ( ! $this->auth || ! $this->auth->has_access() ) {
587 wp_send_json_error( array( 'message' => 'Authentication required' ) );
588 return;
589 }
590
591 // Get new password
592 $new_password = isset( $_POST['dev_panel_password'] ) ? sanitize_text_field( $_POST['dev_panel_password'] ) : '';
593
594 if ( empty( $new_password ) ) {
595 wp_send_json_error( array( 'message' => 'Password cannot be empty' ) );
596 return;
597 }
598
599 if ( strlen( $new_password ) < 6 ) {
600 wp_send_json_error( array( 'message' => 'Password must be at least 6 characters long' ) );
601 return;
602 }
603
604 $result = update_option( self::PASSWORD_OPTION, $new_password );
605
606 if ( $result ) {
607 wp_send_json_success( array( 'message' => 'Password updated successfully' ) );
608 } else {
609 wp_send_json_error( array( 'message' => 'Failed to update password' ) );
610 }
611 }
612 }
613