PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.21
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.21
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 / includes / class-metasync-debug-mode-manager.php

class-metasync-debug-mode-manager.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.21, at includes/class-metasync-debug-mode-manager.php

859 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MetaSync Debug Mode Manager
4 *
5 * Manages debug mode with automatic disable, safety limits, and log rotation.
6 * Implements time-based auto-disable and file size limits to prevent log file growth issues.
7 *
8 * @package MetaSync
9 * @subpackage MetaSync/includes
10 * @since 2.5.15
11 */
12
13 // Prevent direct access
14 if (!defined('ABSPATH')) {
15 exit;
16 }
17
18 /**
19 * Class Metasync_Debug_Mode_Manager
20 *
21 * Handles all debug mode functionality including:
22 * - Time-based auto-disable (24 hours default)
23 * - File size monitoring and rotation (10MB max)
24 * - Manual override for indefinite debug mode
25 * - Admin notifications for state changes
26 * - Dashboard widget for status display
27 *
28 * @since 2.5.15
29 */
30 class Metasync_Debug_Mode_Manager
31 {
32 /**
33 * Singleton instance
34 *
35 * @var Metasync_Debug_Mode_Manager|null
36 */
37 private static $instance = null;
38
39 /**
40 * Maximum debug log file size in bytes (10MB)
41 *
42 * @var int
43 */
44 const MAX_LOG_SIZE = 10485760; // 10MB in bytes
45
46 /**
47 * Debug mode duration in seconds (24 hours)
48 *
49 * @var int
50 */
51 const DEBUG_DURATION = 86400; // 24 hours in seconds
52
53 /**
54 * Maximum number of rotated log files to keep
55 *
56 * @var int
57 */
58 const MAX_ROTATED_LOGS = 1; // Keep current + 1 old
59
60 /**
61 * Cron hook name for checking debug limits
62 *
63 * @var string
64 */
65 const CRON_HOOK = 'metasync_check_debug_limits';
66
67 /**
68 * Option key for debug mode settings
69 *
70 * @var string
71 */
72 const OPTION_KEY = 'metasync_debug_mode_settings';
73
74 /**
75 * Transient key for admin notices
76 *
77 * @var string
78 */
79 const NOTICE_TRANSIENT = 'metasync_debug_mode_notices';
80
81 /**
82 * Debug log file path
83 *
84 * @var string
85 */
86 private $log_file_path;
87
88 /**
89 * Get singleton instance
90 *
91 * @return Metasync_Debug_Mode_Manager
92 */
93 public static function get_instance()
94 {
95 if (self::$instance === null) {
96 self::$instance = new self();
97 }
98 return self::$instance;
99 }
100
101 /**
102 * Constructor - Initialize hooks and settings
103 */
104 private function __construct()
105 {
106 $this->log_file_path = WP_CONTENT_DIR . '/debug.log';
107 $this->init_hooks();
108 }
109
110 /**
111 * Initialize WordPress hooks
112 *
113 * @return void
114 */
115 private function init_hooks()
116 {
117 // Register cron schedules
118 add_filter('cron_schedules', array($this, 'register_cron_schedules'));
119
120 // Schedule cron job on activation
121 add_action('init', array($this, 'maybe_schedule_cron'));
122
123 // Cron job handler
124 add_action(self::CRON_HOOK, array($this, 'check_debug_limits'));
125
126 // Admin notices
127 add_action('admin_notices', array($this, 'display_admin_notices'));
128
129 // Dashboard widget
130 add_action('wp_dashboard_setup', array($this, 'register_dashboard_widget'));
131
132 // REST API endpoints
133 add_action('rest_api_init', array($this, 'register_rest_routes'));
134
135 // Enqueue admin scripts
136 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
137 }
138
139 /**
140 * Register custom cron schedules
141 *
142 * @param array $schedules Existing schedules
143 * @return array Modified schedules
144 */
145 public function register_cron_schedules($schedules)
146 {
147 // Ensure we don't override existing schedule
148 if (!isset($schedules['hourly'])) {
149 $schedules['hourly'] = array(
150 'interval' => 3600,
151 'display' => __('Once Hourly', 'metasync')
152 );
153 }
154 return $schedules;
155 }
156
157 /**
158 * Schedule cron job if not already scheduled
159 *
160 * @return void
161 */
162 public function maybe_schedule_cron()
163 {
164 if (!wp_next_scheduled(self::CRON_HOOK)) {
165 wp_schedule_event(time(), 'hourly', self::CRON_HOOK);
166 }
167 }
168
169 /**
170 * Enable debug mode
171 *
172 * @param bool $indefinite Whether to enable indefinitely
173 * @return bool Success status
174 */
175 public function enable_debug_mode($indefinite = false)
176 {
177 $settings = array(
178 'enabled' => true,
179 'enabled_at' => current_time('timestamp'),
180 'indefinite' => $indefinite,
181 'extended_count' => 0
182 );
183
184 $result = update_option(self::OPTION_KEY, $settings);
185
186 if ($result) {
187 // Enable WP_DEBUG constants via ConfigController
188 $this->update_wp_debug_constants(true);
189
190 // Ensure cron job is scheduled (safety net)
191 if (!wp_next_scheduled(self::CRON_HOOK)) {
192 wp_schedule_event(time(), 'hourly', self::CRON_HOOK);
193 }
194
195 // Clear any previous notices
196 delete_transient(self::NOTICE_TRANSIENT);
197
198 // Log the action
199 error_log('MetaSync: Debug mode enabled' . ($indefinite ? ' (indefinite)' : ' (24 hours)'));
200 }
201
202 return $result;
203 }
204
205 /**
206 * Disable debug mode
207 *
208 * @param string $reason Reason for disabling
209 * @return bool Success status
210 */
211 public function disable_debug_mode($reason = 'manual')
212 {
213 $settings = $this->get_settings();
214 $settings['enabled'] = false;
215 $settings['disabled_at'] = current_time('timestamp');
216 $settings['disabled_reason'] = $reason;
217
218 $result = update_option(self::OPTION_KEY, $settings);
219
220 if ($result) {
221 // Disable WP_DEBUG constants via ConfigController
222 $this->update_wp_debug_constants(false);
223
224 // Add admin notice
225 $this->add_notice(
226 'Debug mode has been disabled (' . $reason . ').',
227 'info'
228 );
229
230 // Log the action
231 error_log('MetaSync: Debug mode disabled - ' . $reason);
232 }
233
234 return $result;
235 }
236
237 /**
238 * Extend debug mode for another 24 hours
239 *
240 * @return bool Success status
241 */
242 public function extend_debug_mode()
243 {
244 $settings = $this->get_settings();
245
246 if (!$settings['enabled']) {
247 return false;
248 }
249
250 $settings['enabled_at'] = current_time('timestamp');
251 $settings['extended_count'] = ($settings['extended_count'] ?? 0) + 1;
252
253 $result = update_option(self::OPTION_KEY, $settings);
254
255 if ($result) {
256 $this->add_notice(
257 'Debug mode extended for another 24 hours.',
258 'success'
259 );
260 }
261
262 return $result;
263 }
264
265 /**
266 * Toggle indefinite mode
267 *
268 * @param bool $enable Whether to enable indefinite mode
269 * @return bool Success status
270 */
271 public function toggle_indefinite_mode($enable)
272 {
273 $settings = $this->get_settings();
274 $settings['indefinite'] = $enable;
275
276 return update_option(self::OPTION_KEY, $settings);
277 }
278
279 /**
280 * Check debug limits (called by cron)
281 *
282 * @return void
283 */
284 public function check_debug_limits()
285 {
286 $this->check_time_limit();
287 $this->check_file_size_limit();
288 }
289
290 /**
291 * Check if debug mode has exceeded time limit
292 *
293 * @return void
294 */
295 private function check_time_limit()
296 {
297 $settings = $this->get_settings();
298
299 // Skip if debug mode is disabled or in indefinite mode
300 if (!$settings['enabled'] || $settings['indefinite']) {
301 return;
302 }
303
304 $enabled_at = $settings['enabled_at'];
305 $current_time = current_time('timestamp');
306 $elapsed_time = $current_time - $enabled_at;
307
308 // Check if 24 hours have passed
309 if ($elapsed_time >= self::DEBUG_DURATION) {
310 $this->disable_debug_mode('auto_expired');
311 $this->add_notice(
312 'Debug mode auto-disabled after 24 hours.',
313 'warning'
314 );
315 }
316 }
317
318 /**
319 * Check if debug log file has exceeded size limit
320 *
321 * @return void
322 */
323 private function check_file_size_limit()
324 {
325 if (!file_exists($this->log_file_path)) {
326 return;
327 }
328
329 $file_size = filesize($this->log_file_path);
330
331 if ($file_size >= self::MAX_LOG_SIZE) {
332 $this->rotate_log_file();
333 $this->add_notice(
334 sprintf('Debug log rotated due to size limit (%s).', $this->format_bytes(self::MAX_LOG_SIZE)),
335 'info'
336 );
337 }
338 }
339
340 /**
341 * Rotate debug log file
342 *
343 * @return bool Success status
344 */
345 private function rotate_log_file()
346 {
347 if (!file_exists($this->log_file_path)) {
348 return false;
349 }
350
351 $backup_path = $this->log_file_path . '.old';
352
353 // Remove existing .old file if it exists
354 if (file_exists($backup_path)) {
355 @unlink($backup_path);
356 }
357
358 // Rename current log to .old
359 $result = @rename($this->log_file_path, $backup_path);
360
361 if ($result) {
362 // Create new empty log file
363 @file_put_contents($this->log_file_path, '');
364 error_log('MetaSync: Debug log rotated - exceeded 10MB limit');
365 }
366
367 // Cleanup old rotations (keep only MAX_ROTATED_LOGS)
368 $this->cleanup_old_rotations();
369
370 return $result;
371 }
372
373 /**
374 * Cleanup old log rotations
375 *
376 * @return void
377 */
378 private function cleanup_old_rotations()
379 {
380 $log_dir = dirname($this->log_file_path);
381 $log_basename = basename($this->log_file_path);
382 $pattern = $log_dir . '/' . $log_basename . '.old*';
383
384 $old_logs = glob($pattern);
385
386 if (count($old_logs) > self::MAX_ROTATED_LOGS) {
387 // Sort by modification time (oldest first)
388 usort($old_logs, function ($a, $b) {
389 return filemtime($a) - filemtime($b);
390 });
391
392 // Delete oldest files, keep only MAX_ROTATED_LOGS
393 $to_delete = array_slice($old_logs, 0, count($old_logs) - self::MAX_ROTATED_LOGS);
394 foreach ($to_delete as $old_log) {
395 @unlink($old_log);
396 }
397 }
398 }
399
400 /**
401 * Get current debug mode settings
402 *
403 * @return array Debug mode settings
404 */
405 public function get_settings()
406 {
407 $defaults = array(
408 'enabled' => false,
409 'enabled_at' => 0,
410 'indefinite' => false,
411 'extended_count' => 0,
412 'disabled_at' => 0,
413 'disabled_reason' => ''
414 );
415
416 $settings = get_option(self::OPTION_KEY, $defaults);
417
418 return wp_parse_args($settings, $defaults);
419 }
420
421 /**
422 * Get debug mode status for dashboard widget
423 *
424 * @return array Status information
425 */
426 public function get_status()
427 {
428 $settings = $this->get_settings();
429 $file_size = file_exists($this->log_file_path) ? filesize($this->log_file_path) : 0;
430
431 $status = array(
432 'enabled' => $settings['enabled'],
433 'indefinite' => $settings['indefinite'],
434 'enabled_at' => $settings['enabled_at'],
435 'time_remaining' => 0,
436 'time_remaining_formatted' => 'N/A',
437 'log_file_size' => $file_size,
438 'log_file_size_formatted' => $this->format_bytes($file_size),
439 'log_file_path' => $this->log_file_path,
440 'max_log_size' => self::MAX_LOG_SIZE,
441 'max_log_size_formatted' => $this->format_bytes(self::MAX_LOG_SIZE),
442 'percentage_used' => 0
443 );
444
445 if ($settings['enabled'] && !$settings['indefinite']) {
446 $elapsed_time = current_time('timestamp') - $settings['enabled_at'];
447 $time_remaining = max(0, self::DEBUG_DURATION - $elapsed_time);
448 $status['time_remaining'] = $time_remaining;
449 $status['time_remaining_formatted'] = $this->format_time_remaining($time_remaining);
450 } elseif ($settings['enabled'] && $settings['indefinite']) {
451 $status['time_remaining_formatted'] = 'Indefinite';
452 }
453
454 if (self::MAX_LOG_SIZE > 0) {
455 $status['percentage_used'] = min(100, ($file_size / self::MAX_LOG_SIZE) * 100);
456 }
457
458 return $status;
459 }
460
461 /**
462 * Update WP_DEBUG constants via ConfigController
463 *
464 * @param bool $enable Whether to enable or disable
465 * @return void
466 */
467 private function update_wp_debug_constants($enable)
468 {
469 try {
470 update_option('wp_debug_enabled', $enable ? 'true' : 'false');
471 update_option('wp_debug_log_enabled', $enable ? 'true' : 'false');
472
473 $config_controller = new ConfigControllerMetaSync();
474 $config_controller->store();
475 } catch (Exception $e) {
476 error_log('MetaSync: Error updating wp-config.php - ' . $e->getMessage());
477 }
478 }
479
480 /**
481 * Add admin notice
482 *
483 * @param string $message Notice message
484 * @param string $type Notice type (success, warning, error, info)
485 * @return void
486 */
487 private function add_notice($message, $type = 'info')
488 {
489 $notices = get_transient(self::NOTICE_TRANSIENT);
490 if (!is_array($notices)) {
491 $notices = array();
492 }
493
494 $notices[] = array(
495 'message' => $message,
496 'type' => $type,
497 'timestamp' => current_time('timestamp')
498 );
499
500 set_transient(self::NOTICE_TRANSIENT, $notices, DAY_IN_SECONDS);
501 }
502
503 /**
504 * Display admin notices
505 *
506 * @return void
507 */
508 public function display_admin_notices()
509 {
510 $notices = get_transient(self::NOTICE_TRANSIENT);
511
512 if (!is_array($notices) || empty($notices)) {
513 return;
514 }
515
516 foreach ($notices as $notice) {
517 $class = 'notice notice-' . esc_attr($notice['type']) . ' is-dismissible';
518 printf(
519 '<div class="%1$s"><p><strong>MetaSync Debug Mode:</strong> %2$s</p></div>',
520 $class,
521 esc_html($notice['message'])
522 );
523 }
524
525 // Clear notices after displaying
526 delete_transient(self::NOTICE_TRANSIENT);
527 }
528
529 /**
530 * Register dashboard widget
531 *
532 * @return void
533 */
534 public function register_dashboard_widget()
535 {
536 // Only show to users with manage_options capability
537 if (!current_user_can('manage_options')) {
538 return;
539 }
540
541 $status = $this->get_status();
542
543 // Only show widget if debug mode is enabled
544 if (!$status['enabled']) {
545 return;
546 }
547
548 wp_add_dashboard_widget(
549 'metasync_debug_mode_widget',
550 'MetaSync Debug Mode',
551 array($this, 'render_dashboard_widget')
552 );
553 }
554
555 /**
556 * Render dashboard widget
557 *
558 * @return void
559 */
560 public function render_dashboard_widget()
561 {
562 $status = $this->get_status();
563 ?>
564 <div class="metasync-debug-widget">
565 <div class="debug-status">
566 <span class="status-indicator <?php echo $status['enabled'] ? 'active' : 'inactive'; ?>">
567 <?php echo $status['enabled'] ? '⚠️ Active' : '✓ Inactive'; ?>
568 </span>
569 </div>
570
571 <?php if ($status['enabled']): ?>
572 <div class="debug-info">
573 <p>
574 <strong>Auto-disable in:</strong>
575 <span class="time-remaining"><?php echo esc_html($status['time_remaining_formatted']); ?></span>
576 </p>
577 <p>
578 <strong>Log file size:</strong>
579 <span class="file-size">
580 <?php echo esc_html($status['log_file_size_formatted']); ?> /
581 <?php echo esc_html($status['max_log_size_formatted']); ?>
582 </span>
583 </p>
584 <div class="progress-bar">
585 <div class="progress-fill" style="width: <?php echo esc_attr($status['percentage_used']); ?>%"></div>
586 </div>
587 </div>
588
589 <div class="debug-actions">
590 <?php if (!$status['indefinite']): ?>
591 <button type="button" class="button button-secondary" id="metasync-extend-debug">
592 Extend for 24 Hours
593 </button>
594 <?php endif; ?>
595 <button type="button" class="button button-primary" id="metasync-disable-debug">
596 Disable Now
597 </button>
598 </div>
599 <?php endif; ?>
600 </div>
601
602 <style>
603 .metasync-debug-widget {
604 padding: 10px 0;
605 }
606 .debug-status {
607 margin-bottom: 15px;
608 font-size: 16px;
609 }
610 .status-indicator {
611 display: inline-block;
612 padding: 5px 10px;
613 border-radius: 3px;
614 font-weight: 600;
615 }
616 .status-indicator.active {
617 background: #fff3cd;
618 color: #856404;
619 }
620 .status-indicator.inactive {
621 background: #d4edda;
622 color: #155724;
623 }
624 .debug-info p {
625 margin: 8px 0;
626 }
627 .progress-bar {
628 width: 100%;
629 height: 20px;
630 background: #f0f0f0;
631 border-radius: 3px;
632 overflow: hidden;
633 margin: 10px 0;
634 }
635 .progress-fill {
636 height: 100%;
637 background: linear-gradient(90deg, #46b450 0%, #ffb900 70%, #dc3232 100%);
638 transition: width 0.3s ease;
639 }
640 .debug-actions {
641 margin-top: 15px;
642 display: flex;
643 gap: 10px;
644 }
645 .debug-actions button {
646 flex: 1;
647 }
648 </style>
649 <?php
650 }
651
652 /**
653 * Enqueue admin scripts
654 *
655 * @param string $hook Current admin page hook
656 * @return void
657 */
658 public function enqueue_admin_scripts($hook)
659 {
660 // Only enqueue on dashboard
661 if ($hook !== 'index.php') {
662 return;
663 }
664
665 wp_enqueue_script(
666 'metasync-debug-widget',
667 plugin_dir_url(dirname(__FILE__)) . 'admin/js/debug-widget.js',
668 array('jquery'),
669 METASYNC_VERSION,
670 true
671 );
672
673 wp_localize_script('metasync-debug-widget', 'metasyncDebug', array(
674 'ajaxurl' => admin_url('admin-ajax.php'),
675 'nonce' => wp_create_nonce('metasync_debug_mode'),
676 'restUrl' => rest_url('metasync/v1/debug-mode/'),
677 'restNonce' => wp_create_nonce('wp_rest')
678 ));
679 }
680
681 /**
682 * Register REST API routes
683 *
684 * @return void
685 */
686 public function register_rest_routes()
687 {
688 register_rest_route('metasync/v1', '/debug-mode/status', array(
689 'methods' => 'GET',
690 'callback' => array($this, 'rest_get_status'),
691 'permission_callback' => array($this, 'rest_permission_check')
692 ));
693
694 register_rest_route('metasync/v1', '/debug-mode/enable', array(
695 'methods' => 'POST',
696 'callback' => array($this, 'rest_enable_debug'),
697 'permission_callback' => array($this, 'rest_permission_check')
698 ));
699
700 register_rest_route('metasync/v1', '/debug-mode/disable', array(
701 'methods' => 'POST',
702 'callback' => array($this, 'rest_disable_debug'),
703 'permission_callback' => array($this, 'rest_permission_check')
704 ));
705
706 register_rest_route('metasync/v1', '/debug-mode/extend', array(
707 'methods' => 'POST',
708 'callback' => array($this, 'rest_extend_debug'),
709 'permission_callback' => array($this, 'rest_permission_check')
710 ));
711 }
712
713 /**
714 * REST API permission check
715 *
716 * @return bool
717 */
718 public function rest_permission_check()
719 {
720 return current_user_can('manage_options');
721 }
722
723 /**
724 * REST API: Get debug mode status
725 *
726 * @return WP_REST_Response
727 */
728 public function rest_get_status()
729 {
730 return new WP_REST_Response($this->get_status(), 200);
731 }
732
733 /**
734 * REST API: Enable debug mode
735 *
736 * @param WP_REST_Request $request
737 * @return WP_REST_Response
738 */
739 public function rest_enable_debug($request)
740 {
741 $indefinite = $request->get_param('indefinite') === true;
742 $result = $this->enable_debug_mode($indefinite);
743
744 if ($result) {
745 return new WP_REST_Response(array(
746 'success' => true,
747 'message' => 'Debug mode enabled',
748 'status' => $this->get_status()
749 ), 200);
750 }
751
752 return new WP_REST_Response(array(
753 'success' => false,
754 'message' => 'Failed to enable debug mode'
755 ), 500);
756 }
757
758 /**
759 * REST API: Disable debug mode
760 *
761 * @return WP_REST_Response
762 */
763 public function rest_disable_debug()
764 {
765 $result = $this->disable_debug_mode('manual');
766
767 if ($result) {
768 return new WP_REST_Response(array(
769 'success' => true,
770 'message' => 'Debug mode disabled',
771 'status' => $this->get_status()
772 ), 200);
773 }
774
775 return new WP_REST_Response(array(
776 'success' => false,
777 'message' => 'Failed to disable debug mode'
778 ), 500);
779 }
780
781 /**
782 * REST API: Extend debug mode
783 *
784 * @return WP_REST_Response
785 */
786 public function rest_extend_debug()
787 {
788 $result = $this->extend_debug_mode();
789
790 if ($result) {
791 return new WP_REST_Response(array(
792 'success' => true,
793 'message' => 'Debug mode extended for 24 hours',
794 'status' => $this->get_status()
795 ), 200);
796 }
797
798 return new WP_REST_Response(array(
799 'success' => false,
800 'message' => 'Failed to extend debug mode'
801 ), 500);
802 }
803
804 /**
805 * Format bytes to human-readable format
806 *
807 * @param int $bytes File size in bytes
808 * @return string Formatted size
809 */
810 private function format_bytes($bytes)
811 {
812 if ($bytes == 0) {
813 return '0 B';
814 }
815
816 $units = array('B', 'KB', 'MB', 'GB');
817 $factor = floor((strlen($bytes) - 1) / 3);
818
819 return sprintf('%.2f %s', $bytes / pow(1024, $factor), $units[$factor]);
820 }
821
822 /**
823 * Format time remaining to human-readable format
824 *
825 * @param int $seconds Time in seconds
826 * @return string Formatted time
827 */
828 private function format_time_remaining($seconds)
829 {
830 if ($seconds <= 0) {
831 return 'Expired';
832 }
833
834 $hours = floor($seconds / 3600);
835 $minutes = floor(($seconds % 3600) / 60);
836
837 if ($hours > 0) {
838 return sprintf('%d hours %d minutes', $hours, $minutes);
839 }
840
841 return sprintf('%d minutes', $minutes);
842 }
843
844 /**
845 * Uninstall - Clean up options and cron jobs
846 *
847 * @return void
848 */
849 public static function uninstall()
850 {
851 // Remove scheduled cron
852 wp_clear_scheduled_hook(self::CRON_HOOK);
853
854 // Remove options
855 delete_option(self::OPTION_KEY);
856 delete_transient(self::NOTICE_TRANSIENT);
857 }
858 }
859