PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.12
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.12
2.7.0 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 All 139 releases
metasync / otto-frontend-toolbar / class-metasync-otto-frontend-toolbar.php

class-metasync-otto-frontend-toolbar.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.12, at otto-frontend-toolbar/class-metasync-otto-frontend-toolbar.php

556 lines 17.4 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 * OTTO Frontend Toolbar
9 *
10 * Provides a frontend control bar for authenticated users to enable/disable OTTO on specific pages
11 *
12 * @link https://searchatlas.com
13 * @since 1.0.0
14 *
15 * @package Metasync
16 * @subpackage Metasync/otto-frontend-toolbar
17 */
18
19 /**
20 * Class Metasync_Otto_Frontend_Toolbar
21 *
22 * Manages the frontend toolbar for OTTO control on individual pages/posts
23 */
24 class Metasync_Otto_Frontend_Toolbar {
25
26 /**
27 * Meta key used to store OTTO enabled/disabled state
28 */
29 const META_KEY = '_metasync_otto_disabled';
30
31 /**
32 * Plugin name
33 *
34 * @var string
35 */
36 private $plugin_name;
37
38 /**
39 * Plugin version
40 *
41 * @var string
42 */
43 private $version;
44
45 /**
46 * Initialize the class and set its properties.
47 *
48 * @param string $plugin_name The name of the plugin.
49 * @param string $version The version of this plugin.
50 */
51 public function __construct( $plugin_name, $version ) {
52 $this->plugin_name = $plugin_name;
53 $this->version = $version;
54
55 // Handle toggle on page load
56 add_action( 'init', array( $this, 'handle_toggle_request' ) );
57
58 // Handle AJAX toggle request
59 add_action( 'wp_ajax_metasync_otto_toggle', array( $this, 'ajax_handle_otto_toggle' ) );
60
61 // Handle preview mode - disable user authentication
62 add_filter( 'determine_current_user', array( $this, 'disable_auth_for_preview' ), 999 );
63 add_action( 'send_headers', array( $this, 'clear_auth_cookies_for_preview' ) );
64 }
65
66 /**
67 * Check if current user can manage OTTO
68 *
69 * @param int $post_id Optional. Post ID to check ownership for Authors
70 * @return bool
71 */
72 public function current_user_can_manage_otto( $post_id = 0 ) {
73 // First check if user has plugin access (uses common method from Metasync class)
74 if ( ! Metasync::current_user_has_plugin_access() ) {
75 return false;
76 }
77
78 // Administrators and Editors can manage all posts/pages
79 if ( current_user_can( 'edit_others_posts' ) || current_user_can( 'edit_others_pages' ) ) {
80 return true;
81 }
82
83 // Authors can only manage their own posts
84 if ( current_user_can( 'publish_posts' ) ) {
85 // If no post_id provided, allow (for general permission checks)
86 if ( empty( $post_id ) ) {
87 return true;
88 }
89
90 // Check if the post belongs to the current user
91 $post = get_post( $post_id );
92 if ( $post && $post->post_author == get_current_user_id() ) {
93 return true;
94 }
95 }
96
97 return false;
98 }
99
100 /**
101 * Check if OTTO is disabled for a specific post/page
102 *
103 * @param int $post_id Post ID to check
104 * @return bool True if OTTO is disabled, false if enabled
105 */
106 public static function is_otto_disabled( $post_id ) {
107 $disabled = get_post_meta( $post_id, self::META_KEY, true );
108 return $disabled === '1' || $disabled === 'true';
109 }
110
111 /**
112 * Set OTTO status for a specific post/page
113 *
114 * @param int $post_id Post ID
115 * @param bool $disabled True to disable OTTO, false to enable
116 * @return bool Success status
117 */
118 public function set_otto_status( $post_id, $disabled ) {
119 if ( $disabled ) {
120 return update_post_meta( $post_id, self::META_KEY, '1' );
121 } else {
122 return delete_post_meta( $post_id, self::META_KEY );
123 }
124 }
125
126 /**
127 * Enqueue toolbar styles
128 */
129 public function enqueue_styles() {
130 // Only load on frontend for users with permissions
131 if ( is_admin() ) {
132 return;
133 }
134
135 // Only load on singular posts/pages
136 if ( ! is_singular() ) {
137 return;
138 }
139
140 // Check if toolbar is disabled via settings
141 $metasync_options = get_option( 'metasync_options' );
142 $toolbar_disabled = isset( $metasync_options['general']['otto_disable_preview_button'] ) &&
143 filter_var( $metasync_options['general']['otto_disable_preview_button'], FILTER_VALIDATE_BOOLEAN );
144 if ( $toolbar_disabled ) {
145 return;
146 }
147
148 // Check if user can manage OTTO for this specific post
149 if ( ! $this->current_user_can_manage_otto( get_the_ID() ) ) {
150 return;
151 }
152
153 wp_enqueue_style(
154 $this->plugin_name . '-otto-toolbar',
155 plugin_dir_url( __FILE__ ) . 'css/otto-toolbar.css',
156 array(),
157 $this->version,
158 'all'
159 );
160 }
161
162 /**
163 * Enqueue toolbar scripts
164 */
165 public function enqueue_scripts() {
166 // Only load on frontend for users with permissions
167 if ( is_admin() ) {
168 return;
169 }
170
171 // Only load on singular posts/pages
172 if ( ! is_singular() ) {
173 return;
174 }
175
176 // Check if toolbar is disabled via settings
177 $metasync_options = get_option( 'metasync_options' );
178 $toolbar_disabled = isset( $metasync_options['general']['otto_disable_preview_button'] ) &&
179 filter_var( $metasync_options['general']['otto_disable_preview_button'], FILTER_VALIDATE_BOOLEAN );
180 if ( $toolbar_disabled ) {
181 return;
182 }
183
184 // Check if user can manage OTTO for this specific post
185 if ( ! $this->current_user_can_manage_otto( get_the_ID() ) ) {
186 return;
187 }
188
189 wp_enqueue_script(
190 $this->plugin_name . '-otto-toolbar',
191 plugin_dir_url( __FILE__ ) . 'js/otto-toolbar.js',
192 array( 'jquery' ),
193 $this->version,
194 true
195 );
196
197 // Localize script with API data
198 $metasync_options = get_option( 'metasync_options' );
199 $otto_uuid = isset( $metasync_options['general']['otto_pixel_uuid'] ) ? $metasync_options['general']['otto_pixel_uuid'] : '';
200 $whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
201
202 # Use endpoint manager to get the correct API URL
203 $api_url = class_exists('Metasync_Endpoint_Manager')
204 ? Metasync_Endpoint_Manager::get_endpoint('OTTO_URL_DETAILS')
205 : 'https://sa.searchatlas.com/api/v2/otto-url-details';
206
207 # Ensure trailing slash
208 $api_url = rtrim($api_url, '/') . '/';
209
210 wp_localize_script(
211 $this->plugin_name . '-otto-toolbar',
212 'metasyncOttoDebug',
213 array(
214 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
215 'apiUrl' => $api_url,
216 'ottoUuid' => $otto_uuid,
217 'currentUrl' => get_permalink( get_the_ID() ),
218 'nonce' => wp_create_nonce( 'metasync_otto_debug' ),
219 'ottoName' => $whitelabel_otto_name,
220 )
221 );
222 }
223
224 /**
225 * Render sticky debug bar at bottom left
226 */
227 public function render_debug_bar() {
228 // Only show on frontend for users with permissions
229 if ( is_admin() ) {
230 return;
231 }
232
233 // Only show on singular posts/pages
234 if ( ! is_singular() ) {
235 return;
236 }
237
238 // Check if toolbar is disabled via settings
239 $metasync_options = get_option( 'metasync_options' );
240 $toolbar_disabled = isset( $metasync_options['general']['otto_disable_preview_button'] ) &&
241 filter_var( $metasync_options['general']['otto_disable_preview_button'], FILTER_VALIDATE_BOOLEAN );
242 if ( $toolbar_disabled ) {
243 return;
244 }
245
246 // Check if user can manage OTTO for this specific post
247 if ( ! $this->current_user_can_manage_otto( get_the_ID() ) ) {
248 return;
249 }
250
251 $post_id = get_the_ID();
252 $is_disabled = self::is_otto_disabled( $post_id );
253 $status_class = $is_disabled ? 'otto-disabled' : 'otto-enabled';
254 $whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
255 $status_text = $is_disabled ? $whitelabel_otto_name . ' Disabled' : $whitelabel_otto_name . ' Enabled';
256
257 ?>
258 <div id="metasync-otto-debug-bar" class="metasync-otto-debug-bar <?php echo esc_attr( $status_class ); ?>">
259 <div class="otto-debug-status">
260 <span class="otto-status-indicator"></span>
261 <span class="otto-status-text"><?php echo esc_html( $status_text ); ?></span>
262 </div>
263 <?php if ( ! $is_disabled ) : ?>
264 <button type="button" class="otto-preview-btn" id="otto-preview-btn">
265 <span class="dashicons dashicons-visibility"></span>
266 Preview Original
267 </button>
268 <?php endif; ?>
269 <button type="button" class="otto-debug-btn" id="otto-debug-btn">
270 <span class="dashicons dashicons-admin-tools"></span>
271 Debug
272 </button>
273 <button type="button" class="otto-close-btn" id="otto-close-btn" aria-label="<?php esc_attr_e( 'Close toolbar', 'metasync' ); ?>">
274 &times;
275 </button>
276 </div>
277
278 <!-- Debug Tray (hidden by default) -->
279 <div id="metasync-otto-debug-tray" class="metasync-otto-debug-tray">
280 <div class="otto-debug-tray-header">
281 <h3><?php echo esc_html( $whitelabel_otto_name ); ?> Debug - Comparison Data</h3>
282 <button type="button" class="otto-debug-tray-close" id="otto-debug-tray-close">
283 <span class="dashicons dashicons-no-alt"></span>
284 </button>
285 </div>
286 <div class="otto-debug-tray-content" id="otto-debug-tray-content">
287 <div class="otto-debug-loading">
288 <div class="otto-loading-spinner"></div>
289 <p>Loading comparison data...</p>
290 </div>
291 </div>
292 <div class="otto-debug-tray-footer">
293 <div class="otto-toggle-container">
294 <label class="otto-toggle-label">
295 <span class="otto-toggle-text"><?php echo esc_html( $whitelabel_otto_name ); ?> Status:</span>
296 <span class="otto-toggle-status-text"><?php echo $is_disabled ? 'Disabled' : 'Enabled'; ?></span>
297 </label>
298 <label class="otto-toggle-switch">
299 <input type="checkbox" id="otto-debug-toggle" <?php checked( ! $is_disabled ); ?> data-post-id="<?php echo esc_attr( $post_id ); ?>">
300 <span class="otto-toggle-slider"></span>
301 </label>
302 </div>
303 </div>
304 </div>
305
306 <!-- Preview iframe overlay (hidden by default) -->
307 <div id="metasync-otto-preview-overlay" class="metasync-otto-preview-overlay">
308 <div class="otto-preview-header">
309 <span class="otto-preview-title">
310 <span class="dashicons dashicons-visibility"></span>
311 Preview: Original Content (<?php echo esc_html( $whitelabel_otto_name ); ?> Disabled)
312 </span>
313 <button type="button" class="otto-preview-close" id="otto-preview-close">
314 <span class="dashicons dashicons-no-alt"></span>
315 Close Preview
316 </button>
317 </div>
318 <div class="otto-preview-loading" id="otto-preview-loading">
319 <div class="otto-loading-spinner"></div>
320 <p>Loading preview...</p>
321 </div>
322 <iframe id="metasync-otto-preview-iframe" class="metasync-otto-preview-iframe" src="" style="opacity: 0;" scrolling="yes" frameborder="0"></iframe>
323 </div>
324 <?php
325 }
326
327 /**
328 * Add OTTO control to WordPress admin bar
329 */
330 public function add_admin_bar_menu( $wp_admin_bar ) {
331 // Only show on frontend for users with permissions
332 if ( is_admin() ) {
333 return;
334 }
335
336 // Only show on singular posts/pages
337 if ( ! is_singular() ) {
338 return;
339 }
340
341 // Check if toolbar is disabled via settings
342 $metasync_options = get_option( 'metasync_options' );
343 $toolbar_disabled = isset( $metasync_options['general']['otto_disable_preview_button'] ) &&
344 filter_var( $metasync_options['general']['otto_disable_preview_button'], FILTER_VALIDATE_BOOLEAN );
345 if ( $toolbar_disabled ) {
346 return;
347 }
348
349 $post_id = get_the_ID();
350
351 // Check if user can manage OTTO for this specific post
352 if ( ! $this->current_user_can_manage_otto( $post_id ) ) {
353 return;
354 }
355 $is_disabled = self::is_otto_disabled( $post_id );
356 $whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
357
358 // Set status-based styling
359 $status_class = $is_disabled ? 'metasync-otto-disabled' : 'metasync-otto-enabled';
360 $icon_class = 'metasync-otto-signal-icon';
361
362 // Add parent menu with status indicator
363 $wp_admin_bar->add_node( array(
364 'id' => 'metasync-otto-control',
365 'title' => '<span class="ab-label">' . esc_html( $whitelabel_otto_name ) . ' Control</span><span class="' . $icon_class . '"></span>',
366 'href' => '#',
367 'meta' => array(
368 'class' => 'metasync-otto-control-menu ' . $status_class,
369 ),
370 ) );
371
372 // Generate toggle URLs with nonce
373 $enable_url = wp_nonce_url(
374 add_query_arg( array(
375 'metasync_otto_action' => 'enable',
376 'post_id' => $post_id
377 ) ),
378 'metasync_otto_toggle_' . $post_id,
379 'metasync_otto_nonce'
380 );
381
382 $disable_url = wp_nonce_url(
383 add_query_arg( array(
384 'metasync_otto_action' => 'disable',
385 'post_id' => $post_id
386 ) ),
387 'metasync_otto_toggle_' . $post_id,
388 'metasync_otto_nonce'
389 );
390
391 // Add Enable OTTO submenu
392 $wp_admin_bar->add_node( array(
393 'id' => 'metasync-otto-enable',
394 'parent' => 'metasync-otto-control',
395 'title' => $is_disabled ? 'Enable ' . esc_html( $whitelabel_otto_name ) : '<strong>✓ ' . esc_html( $whitelabel_otto_name ) . ' Enabled</strong>',
396 'href' => $enable_url,
397 'meta' => array(
398 'class' => 'metasync-otto-toggle ' . ( $is_disabled ? '' : 'active' ),
399 ),
400 ) );
401
402 // Add Disable OTTO submenu
403 $wp_admin_bar->add_node( array(
404 'id' => 'metasync-otto-disable',
405 'parent' => 'metasync-otto-control',
406 'title' => $is_disabled ? '<strong>✓ ' . esc_html( $whitelabel_otto_name ) . ' Disabled</strong>' : 'Disable ' . esc_html( $whitelabel_otto_name ),
407 'href' => $disable_url,
408 'meta' => array(
409 'class' => 'metasync-otto-toggle ' . ( $is_disabled ? 'active' : '' ),
410 ),
411 ) );
412 }
413
414 /**
415 * Disable authentication for preview mode
416 * This allows logged-in users to preview the page as if they were logged out
417 *
418 * @param int|bool $user_id User ID if already determined, false otherwise
419 * @return int|bool Modified user ID or false
420 */
421 public function disable_auth_for_preview( $user_id ) {
422 // Check if this is a preview request
423 if ( isset( $_GET['otto_preview'] ) && $_GET['otto_preview'] === '1' ) {
424 // Return false to indicate no user is logged in
425 return false;
426 }
427
428 return $user_id;
429 }
430
431 /**
432 * Clear authentication cookies for preview mode
433 * Ensures the preview iframe doesn't use the parent page's authentication
434 */
435 public function clear_auth_cookies_for_preview() {
436 // Check if this is a preview request
437 if ( isset( $_GET['otto_preview'] ) && $_GET['otto_preview'] === '1' ) {
438 // Clear WordPress auth cookies for this request only
439 // This prevents the logged-in state from the parent page affecting the iframe
440 $_COOKIE = array_filter( $_COOKIE, function( $key ) {
441 // Remove WordPress auth cookies
442 return strpos( $key, 'wordpress_logged_in_' ) === false &&
443 strpos( $key, 'wordpress_' ) === false &&
444 $key !== 'wp-settings-' . get_current_user_id() &&
445 $key !== 'wp-settings-time-' . get_current_user_id();
446 }, ARRAY_FILTER_USE_KEY );
447 }
448 }
449
450 /**
451 * Handle AJAX toggle request
452 */
453 public function ajax_handle_otto_toggle() {
454 // Verify nonce
455 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'metasync_otto_debug' ) ) {
456 wp_send_json_error( array( 'message' => 'Security check failed.' ) );
457 }
458
459 // Get parameters
460 $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
461 $otto_action = isset( $_POST['otto_action'] ) ? sanitize_text_field( $_POST['otto_action'] ) : '';
462
463 // Validate post
464 if ( ! $post_id || ! get_post( $post_id ) ) {
465 wp_send_json_error( array( 'message' => 'Invalid post ID.' ) );
466 }
467
468 // Check permissions for this specific post
469 $whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
470 if ( ! $this->current_user_can_manage_otto( $post_id ) ) {
471 wp_send_json_error( array( 'message' => 'You do not have permission to manage ' . esc_html( $whitelabel_otto_name ) . ' settings for this post.' ) );
472 }
473
474 // Validate action
475 if ( ! in_array( $otto_action, array( 'enable', 'disable' ), true ) ) {
476 wp_send_json_error( array( 'message' => 'Invalid action.' ) );
477 }
478
479 // Update OTTO status
480 $disabled = ( $otto_action === 'disable' );
481 $success = $this->set_otto_status( $post_id, $disabled );
482
483 if ( $success ) {
484 $message = $disabled ? esc_html( $whitelabel_otto_name ) . ' disabled successfully!' : esc_html( $whitelabel_otto_name ) . ' enabled successfully!';
485 wp_send_json_success( array(
486 'message' => $message,
487 'is_disabled' => $disabled
488 ) );
489 } else {
490 wp_send_json_error( array( 'message' => 'Failed to update OTTO status.' ) );
491 }
492 }
493
494 /**
495 * Handle toggle request via URL parameters
496 */
497 public function handle_toggle_request() {
498 // Check if this is a toggle request
499 if ( ! isset( $_GET['metasync_otto_action'] ) || ! isset( $_GET['post_id'] ) ) {
500 return;
501 }
502
503 // Get parameters
504 $action = sanitize_text_field( $_GET['metasync_otto_action'] );
505 $post_id = absint( $_GET['post_id'] );
506
507 // Verify nonce
508 if ( ! isset( $_GET['metasync_otto_nonce'] ) || ! wp_verify_nonce( $_GET['metasync_otto_nonce'], 'metasync_otto_toggle_' . $post_id ) ) {
509 wp_die( 'Security check failed.' );
510 }
511
512 // Validate post
513 if ( ! $post_id || ! get_post( $post_id ) ) {
514 wp_die( 'Invalid post ID.' );
515 }
516
517 // Check permissions for this specific post
518 $whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
519 if ( ! $this->current_user_can_manage_otto( $post_id ) ) {
520 wp_die( 'You do not have permission to manage ' . esc_html( $whitelabel_otto_name ) . ' settings for this post.' );
521 }
522
523 // Toggle OTTO status
524 if ( $action === 'enable' ) {
525 $this->set_otto_status( $post_id, false );
526 } elseif ( $action === 'disable' ) {
527 $this->set_otto_status( $post_id, true );
528 // WP-315: Clear Divi's per-page CSS cache when OTTO is disabled.
529 // OTTO's HTTP render can corrupt the CSS cache file; clearing it
530 // forces Divi to rebuild it on the next normal render.
531 // Also delete the 24h transient so the fix re-runs when OTTO is re-enabled.
532 $et_cache_dir = WP_CONTENT_DIR . '/et-cache/' . $post_id;
533 if ( is_dir( $et_cache_dir ) ) {
534 $files = glob( $et_cache_dir . '/*' );
535 if ( $files ) {
536 foreach ( $files as $file ) {
537 if ( is_file( $file ) ) {
538 @unlink( $file );
539 }
540 }
541 }
542 }
543 $permalink = get_permalink( $post_id );
544 if ( $permalink ) {
545 delete_transient( 'otto_divi_css_fix_' . md5( $permalink ) );
546 }
547 }
548
549 // Redirect back to the post without query parameters
550 $redirect_url = get_permalink( $post_id );
551 wp_safe_redirect( $redirect_url );
552 exit;
553 }
554 }
555
556