PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 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 All 141 releases
metasync / includes / class-metasync-debug-manager.php

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

1,055 lines 55.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 /**
7 * Debug / Error-Logging manager.
8 *
9 * Extracted from Metasync_Admin to keep the admin class focused on UI concerns.
10 * Handles debug-mode rendering, error-log display, wp-config updates, and
11 * related form-post handlers.
12 *
13 * @package Metasync
14 * @subpackage Metasync/includes
15 */
16 class Metasync_Debug_Manager
17 {
18 /** @var self|null */
19 private static $instance = null;
20
21 /**
22 * Get singleton instance.
23 *
24 * @return self
25 */
26 public static function instance()
27 {
28 if (self::$instance === null) {
29 self::$instance = new self();
30 }
31 return self::$instance;
32 }
33
34 private function __construct() {}
35
36 /**
37 * Get the admin page slug.
38 */
39 private function get_page_slug()
40 {
41 return Metasync_Admin::$page_slug;
42 }
43
44 /**
45 * Read an execution setting (mirrors Metasync_Admin::get_execution_setting).
46 */
47 private function get_execution_setting($key, $default = null)
48 {
49 $settings = get_option('metasync_execution_settings', array());
50 $defaults = array(
51 'max_execution_time' => 30,
52 'max_memory_limit' => 256,
53 'log_batch_size' => 1000,
54 'action_scheduler_batches' => 1,
55 'otto_rate_limit' => 10,
56 'queue_cleanup_days' => 31
57 );
58
59 if (empty($settings)) {
60 return isset($defaults[$key]) ? $defaults[$key] : $default;
61 }
62
63 return isset($settings[$key]) ? $settings[$key] : (isset($defaults[$key]) ? $defaults[$key] : $default);
64 }
65
66 // ------------------------------------------------------------------
67 // Error-log page (standalone page)
68 // ------------------------------------------------------------------
69
70 /**
71 * Display the standalone Error Log admin page.
72 *
73 * @param Metasync_Admin $admin The admin instance (needed for header/nav rendering).
74 */
75 public function metasync_display_error_log($admin) {
76 $log_file = Metasync_Data_Store::file_path('plugin_errors.log');
77 if (false === $log_file) {
78 $log_file = '';
79 }
80 if (!Metasync::current_user_has_plugin_access()) {
81 return;
82 }
83
84 $can_manage_debug_settings = current_user_can('manage_options');
85 if (isset($_POST['wp_debug_nonce']) && !$can_manage_debug_settings) {
86 return;
87 }
88
89 if ($can_manage_debug_settings &&
90 isset($_POST['wp_debug_log_enabled']) &&
91 isset($_POST['wp_debug_enabled']) &&
92 isset($_POST['wp_debug_display_enabled']) &&
93 isset($_POST['wp_debug_nonce']) &&
94 wp_verify_nonce($_POST['wp_debug_nonce'], 'metasync_wp_debug_settings')) {
95
96 $wp_debug = in_array($_POST['wp_debug_enabled'], ['true', 'false']) ? $_POST['wp_debug_enabled'] : 'false';
97 $wp_debug_log = in_array($_POST['wp_debug_log_enabled'], ['true', 'false']) ? $_POST['wp_debug_log_enabled'] : 'false';
98 $wp_debug_display = in_array($_POST['wp_debug_display_enabled'], ['true', 'false']) ? $_POST['wp_debug_display_enabled'] : 'false';
99
100 update_option('wp_debug_enabled', $wp_debug);
101 update_option('wp_debug_log_enabled', $wp_debug_log);
102 update_option('wp_debug_display_enabled', $wp_debug_display);
103
104 $data = new ConfigControllerMetaSync();
105 $saved = $data->store();
106
107 if ($saved) {
108 add_settings_error(
109 'metasync_messages',
110 'metasync_message',
111 'WordPress debug settings updated successfully.',
112 'updated'
113 );
114 } else {
115 add_settings_error(
116 'metasync_messages',
117 'metasync_message',
118 'Debug settings were saved, but wp-config.php could not be updated: '
119 . ($data->getConfigError() !== '' ? $data->getConfigError() : 'the file is not writable.'),
120 'error'
121 );
122 }
123 } elseif ($can_manage_debug_settings && isset($_POST['wp_debug_nonce']) && !wp_verify_nonce($_POST['wp_debug_nonce'], 'metasync_wp_debug_settings')) {
124 add_settings_error(
125 'metasync_messages',
126 'metasync_message',
127 'Security verification failed. Please try again.',
128 'error'
129 );
130 }
131
132
133 $log_enabled = get_option('metasync_log_enabled', 'yes');
134 $wp_debug_enabled = get_option('wp_debug_enabled', 'false');
135 $wp_debug_log_enabled = get_option('wp_debug_log_enabled', 'false');
136 $wp_debug_display_enabled = get_option('wp_debug_display_enabled', 'false');
137 ?>
138
139 <?php $admin->render_layout_open('Error Logs', 'error_log', 'View and manage WordPress error logs and debug settings.'); ?>
140
141 <!-- Log File Management -->
142 <div class="dashboard-card">
143 <h2>Error Log Management</h2>
144 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Clear WordPress error logs to free up space and remove old entries.</p>
145
146 <form method="post" style="margin-top: 15px;">
147 <input type="hidden" name="clear_log" value="yes" />
148 <?php wp_nonce_field('metasync_clear_log_nonce', 'clear_log_nonce'); ?>
149 <?php submit_button('Clear Error Logs', 'secondary', 'clear-log', false, array('class' => 'button button-secondary')); ?>
150 </form>
151 </div>
152
153 <!-- WordPress Debug Settings -->
154 <form method="post">
155 <?php wp_nonce_field('metasync_wp_debug_settings', 'wp_debug_nonce'); ?>
156 <div class="dashboard-card">
157 <h2>WordPress Debug Configuration</h2>
158 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure WordPress debug settings to control error logging and display.</p>
159 <?php settings_errors('metasync_messages'); ?>
160
161 <table class="form-table">
162 <tr valign="top">
163 <th scope="row">WP_DEBUG</th>
164 <td>
165 <select name="wp_debug_enabled">
166 <option value="false" <?php selected('false', $wp_debug_enabled); ?>>Disabled</option>
167 <option value="true" <?php selected('true', $wp_debug_enabled); ?>>Enabled</option>
168 </select>
169 <p class="description">Enable or disable WordPress debugging mode.</p>
170 </td>
171 </tr>
172 <tr valign="top">
173 <th scope="row">WP_DEBUG_LOG</th>
174 <td>
175 <select name="wp_debug_log_enabled">
176 <option value="false" <?php selected('false', $wp_debug_log_enabled); ?>>Disabled</option>
177 <option value="true" <?php selected('true', $wp_debug_log_enabled); ?>>Enabled</option>
178 </select>
179 <p class="description">Save debug messages to a log file.</p>
180 </td>
181 </tr>
182 <tr valign="top">
183 <th scope="row">WP_DEBUG_DISPLAY</th>
184 <td>
185 <select name="wp_debug_display_enabled">
186 <option value="false" <?php selected('false', $wp_debug_display_enabled); ?>>Disabled</option>
187 <option value="true" <?php selected('true', $wp_debug_display_enabled); ?>>Enabled</option>
188 </select>
189 <p class="description">Display debug messages on the website (not recommended for production).</p>
190 </td>
191 </tr>
192 </table>
193 </div>
194
195 <div class="dashboard-card">
196 <h2>Save Changes</h2>
197 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Apply your WordPress logging configuration changes.</p>
198 <?php submit_button('Save WordPress Logging Settings', 'primary', 'submit', false, array('class' => 'button button-primary')); ?>
199 </div>
200 </form>
201
202 <!-- Error Log Display -->
203 <div class="dashboard-card">
204 <h2>Error Log Contents</h2>
205 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">View the current error log entries for troubleshooting and monitoring.</p>
206
207 <?php
208 if (file_exists($log_file)) {
209 $log_content = file_get_contents($log_file);
210 if (!empty($log_content)) {
211 echo '<div class="dashboard-code-block" style="width: 100%; box-sizing: border-box;">';
212 echo '<pre style="background: var(--dashboard-card-bg); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px; overflow: auto; max-height: 400px; font-family: \'SF Mono\', Monaco, \'Cascadia Code\', \'Roboto Mono\', Consolas, monospace; font-size: 13px; line-height: 1.6; color: var(--dashboard-text-primary); margin: 0; box-shadow: var(--dashboard-shadow-sm); width: 100%; box-sizing: border-box; white-space: pre-wrap; word-wrap: break-word;">';
213 echo esc_html($log_content);
214 echo '</pre>';
215 echo '</div>';
216 } else {
217 echo '<div class="dashboard-empty-state">';
218 echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">�
219 Log file is empty - no errors recorded.</p>';
220 echo '</div>';
221 }
222 } else {
223 echo '<div class="dashboard-empty-state">';
224 echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">📝 No log file found. Error logging may not be enabled.</p>';
225 echo '</div>';
226 }
227 ?>
228 </div>
229 <?php $admin->render_layout_close(); ?>
230 <?php
231 }
232
233 // ------------------------------------------------------------------
234 // wp-config.php manipulation
235 // ------------------------------------------------------------------
236
237 /**
238 * Update wp-config.php debug constants.
239 */
240 public function metasync_update_wp_config() {
241 $wp_config_path = ABSPATH . 'wp-config.php';
242 if (file_exists($wp_config_path) && wp_is_writable($wp_config_path)) {
243 $config_file = file_get_contents($wp_config_path);
244
245 $wp_debug_enabled = get_option('wp_debug_enabled', 'false') === 'true' ? 'true' : 'false';
246 if (preg_match("/define\s*\(\s*['\"]WP_DEBUG['\"]\s*,\s*.*?\s*\)\s*;/", $config_file)) {
247 $config_file = preg_replace("/define\s*\(\s*['\"]WP_DEBUG['\"]\s*,\s*.*?\s*\)\s*;/", "define('WP_DEBUG', $wp_debug_enabled);", $config_file);
248 } else {
249 $config_file = str_replace("/* That's all, stop editing! Happy publishing. */", "define('WP_DEBUG', $wp_debug_enabled);\n\n/* That's all, stop editing! Happy publishing. */", $config_file);
250 }
251
252 $wp_debug_log_enabled = get_option('wp_debug_log_enabled', 'false') === 'true' ? 'true' : 'false';
253 if (preg_match("/define\s*\(\s*['\"]WP_DEBUG_LOG['\"]\s*,\s*.*?\s*\)\s*;/", $config_file)) {
254 $config_file = preg_replace("/define\s*\(\s*['\"]WP_DEBUG_LOG['\"]\s*,\s*.*?\s*\)\s*;/", "define('WP_DEBUG_LOG', $wp_debug_log_enabled);", $config_file);
255 } else {
256 $config_file = str_replace("/* That's all, stop editing! Happy publishing. */", "define('WP_DEBUG_LOG', $wp_debug_log_enabled);\n\n/* That's all, stop editing! Happy publishing. */", $config_file);
257 }
258
259 $wp_debug_display_enabled = get_option('wp_debug_display_enabled', 'false') === 'true' ? 'true' : 'false';
260 if (preg_match("/define\s*\(\s*['\"]WP_DEBUG_DISPLAY['\"]\s*,\s*.*?\s*\)\s*;/", $config_file)) {
261 $config_file = preg_replace("/define\s*\(\s*['\"]WP_DEBUG_DISPLAY['\"]\s*,\s*.*?\s*\)\s*;/","define('WP_DEBUG_DISPLAY', $wp_debug_display_enabled);", $config_file);
262 } else {
263 $config_file = str_replace("/* That's all, stop editing! Happy publishing. */", "define('WP_DEBUG_DISPLAY', $wp_debug_display_enabled);\n\n/* That's all, stop editing! Happy publishing. */", $config_file);
264 }
265
266 // ABSPATH is intentional and verified: the Debug Mode feature
267 // toggles WP_DEBUG constants in wp-config.php itself, the one
268 // file whose location ABSPATH exists to describe. It is not a
269 // plugin-directory or generated-content write, so it stays here
270 // (Plugin Check's ABSPATHDetected warning is a false positive —
271 // wp-config.php cannot live in the uploads directory).
272 file_put_contents($wp_config_path, $config_file);
273 } else {
274 wp_die('The wp-config.php file is not writable. Please check the file permissions.');
275 }
276 }
277
278 // ------------------------------------------------------------------
279 // Options hook
280 // ------------------------------------------------------------------
281
282 /**
283 * Sync plugin file headers when metasync_options is updated.
284 *
285 * @param mixed $old_value The old option value.
286 * @param mixed $new_value The new option value.
287 */
288 public function on_options_updated_sync_file_headers($old_value, $new_value)
289 {
290 $old_general = is_array($old_value) ? ($old_value['general'] ?? []) : [];
291 $new_general = is_array($new_value) ? ($new_value['general'] ?? []) : [];
292
293 $whitelabel_keys = [
294 'white_label_plugin_name',
295 'white_label_plugin_description',
296 'white_label_plugin_author',
297 'white_label_plugin_author_uri',
298 'white_label_plugin_uri',
299 ];
300
301 $changed = false;
302 foreach ($whitelabel_keys as $key) {
303 if (($old_general[$key] ?? '') !== ($new_general[$key] ?? '')) {
304 $changed = true;
305 break;
306 }
307 }
308
309 if ($changed) {
310 if (!current_user_can('manage_options')) {
311 return;
312 }
313
314 if (!class_exists('Metasync_Activator')) {
315 require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-activator.php';
316 }
317 Metasync_Activator::sync_plugin_file_headers();
318 }
319 }
320
321 // ------------------------------------------------------------------
322 // Rendering (Advanced-tab sections)
323 // ------------------------------------------------------------------
324
325 /**
326 * Render debug mode section for inclusion in Advanced settings.
327 */
328 public function render_debug_mode_section()
329 {
330 if (!class_exists('Metasync_Debug_Mode_Manager')) {
331 ?>
332 <div style="background: rgba(255, 193, 7, 0.1); border: 1px solid rgba(255, 193, 7, 0.3); border-radius: 8px; padding: 20px; margin-bottom: 20px;">
333 <p style="color: var(--dashboard-text-primary); margin: 0;">
334 ⚠️ Debug Mode Manager is not available. Please ensure the plugin is properly installed.
335 </p>
336 </div>
337 <?php
338 return;
339 }
340
341 $debug_manager = Metasync_Debug_Mode_Manager::get_instance();
342 $status = $debug_manager->get_status();
343 ?>
344
345 <!-- Debug Mode Status Overview -->
346 <div style="margin-bottom: 30px; padding-top: 20px;">
347 <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Current Status</h4>
348 <div style="background: rgba(255, 255, 255, 0.05); border: 1px solid var(--dashboard-border); padding: 20px; border-radius: 8px;">
349 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
350 <!-- Status Badge -->
351 <div style="padding: 10px; border-left: 4px solid <?php echo $status['enabled'] ? '#ffc107' : '#4caf50'; ?>; background: rgba(<?php echo $status['enabled'] ? '255, 193, 7' : '76, 175, 80'; ?>, 0.1); border-radius: 4px;">
352 <div style="font-size: 12px; color: var(--dashboard-text-secondary); margin-bottom: 5px;">Status</div>
353 <div style="font-weight: 600; color: var(--dashboard-text-primary); font-size: 16px;">
354 <?php echo $status['enabled'] ? '⚠️ Active' : '✓ Inactive'; ?>
355 </div>
356 </div>
357
358 <?php if ($status['enabled']): ?>
359 <!-- Mode Type -->
360 <div style="padding: 10px; border-left: 4px solid #2196f3; background: rgba(33, 150, 243, 0.1); border-radius: 4px;">
361 <div style="font-size: 12px; color: var(--dashboard-text-secondary); margin-bottom: 5px;">Mode</div>
362 <div style="font-weight: 600; color: var(--dashboard-text-primary); font-size: 14px;">
363 <?php echo $status['indefinite'] ? 'Indefinite' : '24-Hour Auto-Disable'; ?>
364 </div>
365 </div>
366
367 <?php if (!$status['indefinite']): ?>
368 <!-- Time Remaining -->
369 <div style="padding: 10px; border-left: 4px solid #9c27b0; background: rgba(156, 39, 176, 0.1); border-radius: 4px;">
370 <div style="font-size: 12px; color: var(--dashboard-text-secondary); margin-bottom: 5px;">Time Remaining</div>
371 <div style="font-weight: 600; color: var(--dashboard-text-primary); font-size: 14px;" class="debug-time-remaining">
372 <?php echo esc_html($status['time_remaining_formatted']); ?>
373 </div>
374 </div>
375 <?php endif; ?>
376
377 <!-- Log File Size -->
378 <div style="padding: 10px; border-left: 4px solid #ff5722; background: rgba(255, 87, 34, 0.1); border-radius: 4px;">
379 <div style="font-size: 12px; color: var(--dashboard-text-secondary); margin-bottom: 5px;">Log File Size</div>
380 <div style="font-weight: 600; color: var(--dashboard-text-primary); font-size: 14px;">
381 <?php echo esc_html($status['log_file_size_formatted']); ?>
382 </div>
383 <div style="font-size: 11px; color: var(--dashboard-text-secondary); margin-top: 2px;">
384 <?php echo number_format($status['percentage_used'], 1); ?>% of <?php echo esc_html($status['max_log_size_formatted']); ?>
385 </div>
386 </div>
387 <?php endif; ?>
388 </div>
389
390 <?php if ($status['enabled']): ?>
391 <!-- Progress Bar -->
392 <div style="margin-top: 15px;">
393 <div style="background: rgba(255, 255, 255, 0.1); height: 8px; border-radius: 4px; overflow: hidden;">
394 <div style="height: 100%; width: <?php echo esc_attr($status['percentage_used']); ?>%; background: linear-gradient(90deg, #4caf50 0%, #ffc107 70%, #f44336 100%); transition: width 0.3s ease;"></div>
395 </div>
396 </div>
397 <?php endif; ?>
398 </div>
399 </div>
400
401 <!-- Debug Mode Controls -->
402 <div style="margin-bottom: 30px;">
403 <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Controls</h4>
404
405 <form method="post" action="<?php echo admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced'); ?>">
406 <input type="hidden" name="metasync_debug_mode_action_advanced" value="1" />
407 <?php wp_nonce_field('metasync_debug_mode_action_advanced', 'metasync_debug_mode_nonce_advanced'); ?>
408
409 <?php if (!$status['enabled']): ?>
410 <!-- Enable Debug Mode -->
411 <div style="background: rgba(255, 255, 255, 0.05); border: 1px solid var(--dashboard-border); padding: 20px; border-radius: 8px; margin-bottom: 15px;">
412 <p style="color: var(--dashboard-text-secondary); margin-top: 0; margin-bottom: 15px;">
413 Activate debug mode to troubleshoot issues. Debug mode will automatically disable after 24 hours unless you enable indefinite mode.
414 </p>
415
416 <label style="display: flex; align-items: center; margin: 15px 0; cursor: pointer;">
417 <input type="checkbox" name="indefinite" value="1" id="indefinite-mode-advanced" style="margin-right: 8px;" />
418 <span style="font-weight: 500; color: var(--dashboard-text-primary);">Keep debug mode enabled indefinitely</span>
419 </label>
420
421 <div id="indefinite-warning-advanced" style="display: none; background: rgba(255, 193, 7, 0.1); border-left: 4px solid #ffc107; padding: 12px; border-radius: 4px; margin: 15px 0;">
422 <strong style="color: var(--dashboard-text-primary);">⚠️ Warning:</strong>
423 <span style="color: var(--dashboard-text-secondary);"> Indefinite debug mode may cause log files to grow without limits. This should only be used for extended troubleshooting sessions.</span>
424 </div>
425
426 <input type="hidden" name="action_type" value="enable" />
427 <button type="submit" class="metasync-btn-primary" style="background: var(--dashboard-gradient-primary); color: #ffffff; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); display: inline-block; width: auto; min-width: 200px;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(0, 0, 0, 0.15)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(0, 0, 0, 0.1)';">
428 🐛 Enable Debug Mode
429 </button>
430 </div>
431
432 <?php else: ?>
433 <!-- Manage Active Debug Mode -->
434 <div style="display: flex; gap: 10px; flex-wrap: wrap;">
435 <?php if (!$status['indefinite']): ?>
436 <div>
437 <input type="hidden" name="action_type" value="extend" />
438 <button type="submit" class="metasync-btn-secondary" style="background: rgba(255, 255, 255, 0.1); color: var(--dashboard-text-primary); border: 1px solid var(--dashboard-border); padding: 10px 20px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; display: inline-block; width: auto;" onmouseover="this.style.background='rgba(255, 255, 255, 0.15)';" onmouseout="this.style.background='rgba(255, 255, 255, 0.1)';">
439 ⏱️ Extend for 24 Hours
440 </button>
441 </div>
442 <?php endif; ?>
443
444 <div>
445 <input type="hidden" name="action_type" value="disable" />
446 <button type="submit" class="metasync-btn-danger" onclick="return confirm('Are you sure you want to disable debug mode?');" style="background: linear-gradient(135deg, #f44336, #d32f2f); color: #ffffff; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); display: inline-block; width: auto;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(0, 0, 0, 0.15)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(0, 0, 0, 0.1)';">
447 ⏹️ Disable Debug Mode Now
448 </button>
449 </div>
450 </div>
451 <?php endif; ?>
452 </form>
453 </div>
454
455 <!-- Configuration Details -->
456 <div style="margin-bottom: 30px;">
457 <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Configuration Details</h4>
458 <div style="background: rgba(255, 255, 255, 0.05); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px;">
459 <table style="width: 100%; border-collapse: collapse;">
460 <tbody>
461 <tr style="border-bottom: 1px solid var(--dashboard-border);">
462 <td style="padding: 10px 0; font-weight: 600; color: var(--dashboard-text-primary);">Maximum Log Size</td>
463 <td style="padding: 10px 0; color: var(--dashboard-text-secondary);"><?php echo esc_html($status['max_log_size_formatted']); ?></td>
464 </tr>
465 <tr style="border-bottom: 1px solid var(--dashboard-border);">
466 <td style="padding: 10px 0; font-weight: 600; color: var(--dashboard-text-primary);">Auto-Disable Duration</td>
467 <td style="padding: 10px 0; color: var(--dashboard-text-secondary);">24 hours</td>
468 </tr>
469 <tr style="border-bottom: 1px solid var(--dashboard-border);">
470 <td style="padding: 10px 0; font-weight: 600; color: var(--dashboard-text-primary);">Log Rotation</td>
471 <td style="padding: 10px 0; color: var(--dashboard-text-secondary);">Automatic when size limit reached</td>
472 </tr>
473 <tr style="border-bottom: 1px solid var(--dashboard-border);">
474 <td style="padding: 10px 0; font-weight: 600; color: var(--dashboard-text-primary);">Rotated Files Kept</td>
475 <td style="padding: 10px 0; color: var(--dashboard-text-secondary);">1 (current + 1 old)</td>
476 </tr>
477 <tr style="border-bottom: 1px solid var(--dashboard-border);">
478 <td style="padding: 10px 0; font-weight: 600; color: var(--dashboard-text-primary);">Check Frequency</td>
479 <td style="padding: 10px 0; color: var(--dashboard-text-secondary);">Hourly (via WP Cron)</td>
480 </tr>
481 <tr>
482 <td style="padding: 10px 0; font-weight: 600; color: var(--dashboard-text-primary);">Log File Path</td>
483 <td style="padding: 10px 0; color: var(--dashboard-text-secondary); font-family: monospace; font-size: 12px; word-break: break-all;">
484 <?php echo esc_html($status['log_file_path']); ?>
485 </td>
486 </tr>
487 </tbody>
488 </table>
489 </div>
490 </div>
491
492 <script>
493 jQuery(document).ready(function($) {
494 $('#indefinite-mode-advanced').on('change', function() {
495 if ($(this).is(':checked')) {
496 $('#indefinite-warning-advanced').slideDown();
497 } else {
498 $('#indefinite-warning-advanced').slideUp();
499 }
500 });
501
502 <?php if ($status['enabled'] && !$status['indefinite']): ?>
503 var initialTimeRemaining = <?php echo $status['time_remaining']; ?>;
504 var hasReloaded = false;
505
506 function updateDebugTimeRemaining() {
507 if (initialTimeRemaining <= 0 || hasReloaded) {
508 return;
509 }
510
511 $.ajax({
512 url: '<?php echo rest_url('metasync/v1/debug-mode/status'); ?>',
513 method: 'GET',
514 beforeSend: function(xhr) {
515 xhr.setRequestHeader('X-WP-Nonce', '<?php echo wp_create_nonce('wp_rest'); ?>');
516 },
517 success: function(response) {
518 console.log('MetaSync Debug Mode Status:', response);
519
520 if (response && typeof response.time_remaining !== 'undefined') {
521 if (response.time_remaining_formatted) {
522 $('.debug-time-remaining').text(response.time_remaining_formatted);
523 }
524
525 if (response.time_remaining <= 0 && initialTimeRemaining > 0 && !hasReloaded) {
526 hasReloaded = true;
527 console.log('Debug mode expired, reloading page...');
528 setTimeout(function() {
529 window.location.reload();
530 }, 2000);
531 }
532 }
533 },
534 error: function(xhr, status, error) {
535 console.error('MetaSync Debug Mode: Failed to update time remaining', error);
536 }
537 });
538 }
539
540 if (initialTimeRemaining > 0) {
541 setTimeout(updateDebugTimeRemaining, 2000);
542 setInterval(updateDebugTimeRemaining, 60000);
543 } else {
544 console.log('Debug mode already expired, skipping AJAX updates');
545 }
546 <?php endif; ?>
547 });
548 </script>
549 <?php
550 }
551
552 /**
553 * Render error log content for inclusion in Advanced settings.
554 */
555 public function render_error_log_content()
556 {
557 ?>
558 <!-- Error Summary Section -->
559 <div style="margin-bottom: 30px; padding-top: 20px;">
560 <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Error Summary</h4>
561 <p style="margin-bottom: 15px; color: var(--dashboard-text-secondary);">View categorized error statistics with counts and last occurrence times.</p>
562
563 <?php
564 if (class_exists('Metasync_Error_Logger')) {
565 $error_summary = Metasync_Error_Logger::get_visible_error_summary();
566
567 if (!empty($error_summary) && is_array($error_summary)) {
568 uasort($error_summary, function($a, $b) {
569 return strtotime($b['last_seen']) - strtotime($a['last_seen']);
570 });
571 ?>
572 <div style="overflow-x: auto; margin-bottom: 20px;">
573 <table class="wp-list-table widefat fixed striped" style="background: var(--dashboard-card-bg); border: 1px solid var(--dashboard-border);">
574 <thead>
575 <tr style="background: var(--dashboard-card-bg);">
576 <th style="padding: 12px; text-align: left; border-bottom: 2px solid var(--dashboard-border); color: var(--dashboard-text-primary); font-weight: 600;">Error Category</th>
577 <th style="padding: 12px; text-align: center; border-bottom: 2px solid var(--dashboard-border); color: var(--dashboard-text-primary); font-weight: 600;">Error Code</th>
578 <th style="padding: 12px; text-align: center; border-bottom: 2px solid var(--dashboard-border); color: var(--dashboard-text-primary); font-weight: 600;">Count</th>
579 <th style="padding: 12px; text-align: left; border-bottom: 2px solid var(--dashboard-border); color: var(--dashboard-text-primary); font-weight: 600;">Last Occurred</th>
580 <th style="padding: 12px; text-align: left; border-bottom: 2px solid var(--dashboard-border); color: var(--dashboard-text-primary); font-weight: 600;">Message</th>
581 </tr>
582 </thead>
583 <tbody>
584 <?php foreach ($error_summary as $key => $error): ?>
585 <tr>
586 <td style="padding: 10px 12px; color: var(--dashboard-text-primary);">
587 <strong><?php echo esc_html(Metasync_Error_Logger::get_display_label($error['category'])); ?></strong>
588 </td>
589 <td style="padding: 10px 12px; text-align: center; color: var(--dashboard-text-secondary); font-family: monospace;">
590 <code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px;"><?php echo esc_html($error['code']); ?></code>
591 </td>
592 <td style="padding: 10px 12px; text-align: center;">
593 <span style="display: inline-block; background: var(--dashboard-accent); color: #ffffff; padding: 4px 10px; border-radius: 12px; font-weight: 600; font-size: 13px;">
594 <?php echo esc_html(number_format($error['count'])); ?>
595 </span>
596 </td>
597 <td style="padding: 10px 12px; color: var(--dashboard-text-secondary); font-size: 13px;">
598 <?php
599 $last_seen = strtotime($error['last_seen']);
600 $time_diff = human_time_diff($last_seen, current_time('timestamp'));
601 echo esc_html($error['last_seen']) . ' <span style="color: var(--dashboard-text-secondary);">(' . $time_diff . ' ago)</span>';
602 ?>
603 </td>
604 <td style="padding: 10px 12px; color: var(--dashboard-text-primary); max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="<?php echo esc_attr($error['message']); ?>">
605 <?php echo esc_html($error['message']); ?>
606 </td>
607 </tr>
608 <?php endforeach; ?>
609 </tbody>
610 </table>
611 </div>
612
613 <form method="post" action="<?php echo admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced'); ?>" style="margin-bottom: 20px;">
614 <input type="hidden" name="clear_error_summary" value="yes" />
615 <?php wp_nonce_field('metasync_clear_error_summary_nonce', 'clear_error_summary_nonce'); ?>
616 <button type="submit" class="button button-secondary" style="background: #dc3232; color: #ffffff; border: none; padding: 8px 16px; border-radius: 4px; font-weight: 500; cursor: pointer;">
617 <span class="dashicons dashicons-trash" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Clear Error Summary
618 </button>
619 </form>
620 <?php
621 } else {
622 ?>
623 <div class="dashboard-empty-state" style="padding: 30px; text-align: center; background: var(--dashboard-card-bg); border: 1px solid var(--dashboard-border); border-radius: 8px;">
624 <p style="color: var(--dashboard-text-secondary); font-style: italic; margin: 0;">
625 �
626 No errors recorded yet. Error summary will appear here once errors are logged.
627 </p>
628 </div>
629 <?php
630 }
631 } else {
632 ?>
633 <div class="dashboard-empty-state" style="padding: 30px; text-align: center; background: rgba(255, 193, 7, 0.1); border: 1px solid rgba(255, 193, 7, 0.3); border-radius: 8px;">
634 <p style="color: var(--dashboard-text-primary); margin: 0;">
635 ⚠️ Error Logger class not available. Please ensure the plugin is properly loaded.
636 </p>
637 </div>
638 <?php
639 }
640 ?>
641 </div>
642
643 <!-- Error Log Management -->
644 <div style="margin-bottom: 30px;">
645 <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Clear Error Logs</h4>
646 <p style="margin-bottom: 15px; color: var(--dashboard-text-secondary);">Clear WordPress error logs to free up space and remove old entries.</p>
647
648 <form method="post" action="<?php echo admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced'); ?>" style="margin-bottom: 20px;">
649 <input type="hidden" name="clear_log" value="yes" />
650 <?php wp_nonce_field('metasync_clear_log_nonce', 'clear_log_nonce'); ?>
651 <button type="submit" class="metasync-btn-primary" style="background: var(--dashboard-gradient-primary); color: #ffffff; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); display: inline-block; width: auto; min-width: 240px; max-width: fit-content;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(0, 0, 0, 0.15)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(0, 0, 0, 0.1)';">
652 🧹 Clear Error Logs
653 </button>
654 </form>
655 </div>
656
657 <!-- WordPress Debug Settings -->
658 <div style="margin-bottom: 30px;">
659 <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">WordPress Debug Configuration</h4>
660 <p style="margin-bottom: 15px; color: var(--dashboard-text-secondary);">Configure WordPress debug settings to control error logging and display.</p>
661
662 <form method="post">
663
664 <?php
665 $wp_debug = defined('WP_DEBUG') && WP_DEBUG;
666 $debug_log = defined('WP_DEBUG_LOG') && WP_DEBUG_LOG;
667 $debug_display = defined('WP_DEBUG_DISPLAY') && WP_DEBUG_DISPLAY;
668 ?>
669
670 <p><strong>Current WordPress Debug Status:</strong></p>
671 <ul>
672 <li>WP_DEBUG: <?php echo $wp_debug ? '�
673 Enabled' : '❌ Disabled'; ?></li>
674 <li>WP_DEBUG_LOG: <?php echo $debug_log ? '�
675 Enabled' : '❌ Disabled'; ?></li>
676 <li>WP_DEBUG_DISPLAY: <?php echo $debug_display ? '�
677 Enabled' : '❌ Disabled'; ?></li>
678 </ul>
679
680 <?php if (!$wp_debug): ?>
681 <p style="color: var(--dashboard-accent);">💡 To enable error logging, add these lines to your wp-config.php file:</p>
682 <div class="metasync-copy-wrap"><pre class="metasync-copy-snippet" style="background: rgba(255, 255, 255, 0.05); border: 1px solid var(--dashboard-border); padding: 10px; border-radius: 4px; color: var(--dashboard-text-primary);">define('WP_DEBUG', true);
683 define('WP_DEBUG_LOG', true);
684 define('WP_DEBUG_DISPLAY', false);</pre><span class="metasync-copy-badge">Copy</span></div>
685 <?php /* click-to-copy for the wp-config snippet above */ ?>
686 <style>
687 .metasync-copy-wrap { position: relative; }
688 .metasync-copy-snippet { cursor: pointer; outline: 1px solid transparent; transition: outline-color .15s ease; }
689 .metasync-copy-wrap:hover .metasync-copy-snippet { outline-color: var(--dashboard-border, #374151); }
690 .metasync-copy-badge { position: absolute; top: 8px; right: 10px; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 4px; pointer-events: none; opacity: 0; transition: opacity .15s ease; background: var(--dashboard-border, #374151); color: var(--dashboard-text-primary, #fff); }
691 .metasync-copy-wrap:hover .metasync-copy-badge { opacity: .85; }
692 .metasync-copy-badge.metasync-copy-done { opacity: 1; background: var(--dashboard-gradient-primary, linear-gradient(135deg, #667eea 0%, #764ba2 100%)); color: #fff; }
693 </style>
694 <script>
695 (function(){
696 if (window.__metasyncCopyInit) return; window.__metasyncCopyInit = true;
697 function fallback(text){ var ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.opacity='0'; document.body.appendChild(ta); ta.focus(); ta.select(); try{document.execCommand('copy');}catch(e){} document.body.removeChild(ta); }
698 function attach(){
699 document.querySelectorAll('.metasync-copy-snippet').forEach(function(pre){
700 if (pre.dataset.copyBound) return; pre.dataset.copyBound='1';
701 pre.setAttribute('title','Click to copy');
702 pre.addEventListener('click', function(){
703 var text = pre.textContent.trim();
704 var wrap = pre.closest('.metasync-copy-wrap') || pre.parentNode;
705 var badge = wrap.querySelector('.metasync-copy-badge');
706 var done = function(){ if(!badge) return; badge.textContent='Copied!'; badge.classList.add('metasync-copy-done'); clearTimeout(badge._t); badge._t=setTimeout(function(){ badge.textContent='Copy'; badge.classList.remove('metasync-copy-done'); },1500); };
707 if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(done).catch(function(){ fallback(text); done(); }); }
708 else { fallback(text); done(); }
709 });
710 });
711 }
712 if (document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', attach); } else { attach(); }
713 })();
714 </script>
715 <?php endif; ?>
716 </form>
717 </div>
718
719 <!-- Error Log Display -->
720 <div style="margin-bottom: 30px;">
721 <h4 style="margin-top: 0; color: var(--dashboard-text-primary);">Error Log Contents</h4>
722 <p style="margin-bottom: 15px; color: var(--dashboard-text-secondary);">View the current error log entries for troubleshooting and monitoring.</p>
723
724 <?php
725 $error_logs = new Metasync_Error_Logs();
726
727 if ($error_logs->can_show_error_logs()):
728 $log_content = $error_logs->get_error_logs(50);
729
730 if (!empty(trim($log_content))):
731 $error_logs->show_copy_button();
732 $error_logs->show_logs();
733 $error_logs->show_info();
734 else: ?>
735 <div class="dashboard-empty-state">
736 <p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">�
737 Log file is empty - no errors recorded.</p>
738 </div>
739 <?php endif;
740 else:
741 $error_message = $error_logs->get_error_message();
742 if (!empty($error_message)): ?>
743 <div class="dashboard-empty-state">
744 <p style="color: var(--dashboard-text-primary); font-weight: bold; text-align: center; padding: 20px; background: rgba(255, 193, 7, 0.1); border: 1px solid rgba(255, 193, 7, 0.3); border-radius: 4px; margin: 20px 0;">
745 ⚠️ <?php echo esc_html($error_message); ?>
746 </p>
747 </div>
748 <?php else: ?>
749 <div class="dashboard-empty-state">
750 <p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">⚠️ Unable to access error log file. Please check permissions.</p>
751 </div>
752 <?php endif;
753 endif; ?>
754 </div>
755 <?php
756 }
757
758 // ------------------------------------------------------------------
759 // Form-post handlers
760 // ------------------------------------------------------------------
761
762 /**
763 * Handle debug mode operations (enable/disable/extend).
764 */
765 public function handle_debug_mode_operations()
766 {
767 if (isset($_POST['metasync_debug_mode_action_advanced'])) {
768 if (!wp_verify_nonce($_POST['metasync_debug_mode_nonce_advanced'], 'metasync_debug_mode_action_advanced')) {
769 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1');
770 wp_safe_redirect($redirect_url);
771 exit;
772 }
773
774 if (!current_user_can('manage_options')) {
775 wp_die('Unauthorized');
776 }
777
778 if (!class_exists('Metasync_Debug_Mode_Manager')) {
779 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&debug_error=1&msg=manager_not_available');
780 wp_safe_redirect($redirect_url);
781 exit;
782 }
783
784 $debug_manager = Metasync_Debug_Mode_Manager::get_instance();
785 $action = sanitize_text_field($_POST['action_type'] ?? '');
786 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced');
787
788 switch ($action) {
789 case 'enable':
790 $indefinite = isset($_POST['indefinite']) && $_POST['indefinite'] === '1';
791 $result = $debug_manager->enable_debug_mode($indefinite);
792
793 $redirect_url = add_query_arg('debug_mode_enabled', '1', $redirect_url);
794 if ($indefinite) {
795 $redirect_url = add_query_arg('indefinite', '1', $redirect_url);
796 }
797 break;
798
799 case 'disable':
800 $result = $debug_manager->disable_debug_mode('manual');
801 if ($result) {
802 $redirect_url = add_query_arg('debug_mode_disabled', '1', $redirect_url);
803 } else {
804 $redirect_url = add_query_arg('debug_error', '1', $redirect_url);
805 }
806 break;
807
808 case 'extend':
809 $result = $debug_manager->extend_debug_mode();
810
811 $redirect_url = add_query_arg('debug_mode_extended', '1', $redirect_url);
812 break;
813
814 default:
815 $redirect_url = add_query_arg('debug_error', '1', $redirect_url);
816 break;
817 }
818
819 wp_safe_redirect($redirect_url);
820 exit;
821 }
822 }
823
824 /**
825 * Handle error log operations (clear).
826 */
827 public function handle_error_log_operations()
828 {
829 if ((isset($_POST['clear_log']) || isset($_POST['clear_error_summary'])) && !current_user_can('manage_options')) {
830 return;
831 }
832
833 if (isset($_POST['clear_log'])) {
834 if (wp_verify_nonce($_POST['clear_log_nonce'], 'metasync_clear_log_nonce')) {
835 $data_dir = Metasync_Data_Store::base_dir();
836 $log_file = Metasync_Data_Store::file_path('plugin_errors.log');
837
838 if (false !== $log_file && file_exists($log_file)) {
839 file_put_contents($log_file, '');
840
841 if (false !== $data_dir) {
842 $backup_files = glob($data_dir . '/plugin_errors.log.old.*');
843 if ($backup_files) {
844 foreach ($backup_files as $backup_file) {
845 @wp_delete_file($backup_file);
846 }
847 }
848 }
849 }
850
851 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&log_cleared=1');
852 wp_safe_redirect($redirect_url);
853 exit;
854 } else {
855 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_error=1');
856 wp_safe_redirect($redirect_url);
857 exit;
858 }
859 }
860
861 if (isset($_POST['clear_error_summary']) && isset($_POST['clear_error_summary_nonce'])) {
862 if (wp_verify_nonce($_POST['clear_error_summary_nonce'], 'metasync_clear_error_summary_nonce')) {
863 if (class_exists('Metasync_Error_Logger')) {
864 Metasync_Error_Logger::clear_error_summary();
865 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_cleared=1');
866 wp_safe_redirect($redirect_url);
867 exit;
868 } else {
869 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1');
870 wp_safe_redirect($redirect_url);
871 exit;
872 }
873 } else {
874 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&error_summary_error=1');
875 wp_safe_redirect($redirect_url);
876 exit;
877 }
878 }
879 }
880
881 /**
882 * Handle clear all settings operations.
883 */
884 public function handle_clear_all_settings()
885 {
886 if (isset($_POST['clear_all_settings']) && !current_user_can('manage_options')) {
887 return;
888 }
889
890 if (isset($_POST['clear_all_settings'])) {
891 if (wp_verify_nonce($_POST['clear_all_settings_nonce'], 'metasync_clear_all_settings_nonce')) {
892
893 $metasync_options_to_clear = [
894 'metasync_options',
895 'metasync_options_instant_indexing',
896 'metasync_options_bing_instant_indexing',
897 'metasync_otto_crawldata',
898 'metasync_logging_data',
899 'metasync_wp_sa_connect_token',
900 'wp_debug_enabled',
901 'wp_debug_log_enabled',
902 'wp_debug_display_enabled',
903 // Media Optimization settings & cache
904 'metasync_media_optimization',
905 'metasync_batch_optimize_queue',
906 'metasync_batch_optimize_progress',
907 'metasync_batch_optimize_settings',
908 // Code Minification settings
909 'metasync_code_minification',
910 ];
911
912 $cleared_count = 0;
913 foreach ($metasync_options_to_clear as $option_name) {
914 if (get_option($option_name) !== false) {
915 delete_option($option_name);
916 $cleared_count++;
917 }
918 }
919
920 $transients_to_clear = [
921 'metasync_heartbeat_status_cache',
922 ];
923
924 foreach ($transients_to_clear as $transient_name) {
925 delete_transient($transient_name);
926 }
927
928 $cron_hooks_to_clear = [
929 'metasync_heartbeat_cron_check',
930 'metasync_media_batch_optimize_cron',
931 ];
932
933 foreach ($cron_hooks_to_clear as $cron_hook) {
934 $timestamp = wp_next_scheduled($cron_hook);
935 if ($timestamp) {
936 wp_unschedule_event($timestamp, $cron_hook);
937 }
938 }
939
940 $new_plugin_auth_token = wp_generate_password(32, false, false);
941
942 $fresh_options = [
943 'general' => [
944 'apikey' => $new_plugin_auth_token
945 ]
946 ];
947
948 update_option('metasync_options', $fresh_options);
949
950 Metasync::log_api_key_event('settings_reset', 'plugin_auth_token', array(
951 'options_cleared_count' => $cleared_count,
952 'new_token_prefix' => substr($new_plugin_auth_token, 0, 8) . '...',
953 'triggered_by' => 'settings_reset_action'
954 ), 'info');
955
956 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&settings_cleared=1');
957 wp_safe_redirect($redirect_url);
958 exit;
959 } else {
960 $redirect_url = admin_url('admin.php?page=' . $this->get_page_slug() . '&tab=advanced&clear_settings_error=1');
961 wp_safe_redirect($redirect_url);
962 exit;
963 }
964 }
965 }
966
967 // ------------------------------------------------------------------
968 // Log-file helpers
969 // ------------------------------------------------------------------
970
971 /**
972 * Get error log content for display.
973 */
974 public function get_error_log_content()
975 {
976 $execution_time = $this->get_execution_setting('max_execution_time');
977 if (function_exists('set_time_limit')) {
978 @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API
979 }
980
981 $log_file = WP_CONTENT_DIR . '/debug.log';
982
983 if (!file_exists($log_file) || !is_readable($log_file)) {
984 return false;
985 }
986
987 $batch_size = $this->get_execution_setting('log_batch_size');
988
989 $file_size = filesize($log_file);
990 if ($file_size > 10 * 1024 * 1024) {
991 return $this->get_log_tail($log_file, $batch_size);
992 }
993
994 $content = file_get_contents($log_file);
995 if ($content === false) {
996 return false;
997 }
998
999 $lines = explode("\n", $content);
1000 $recent_lines = array_slice($lines, -$batch_size);
1001
1002 return implode("\n", $recent_lines);
1003 }
1004
1005 /**
1006 * Memory-efficient function to get last N lines from a large file.
1007 */
1008 public function get_log_tail($file_path, $lines = null)
1009 {
1010 $execution_time = $this->get_execution_setting('max_execution_time');
1011 if (function_exists('set_time_limit')) {
1012 @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API
1013 }
1014
1015 if ($lines === null) {
1016 $lines = $this->get_execution_setting('log_batch_size');
1017 }
1018
1019 $handle = fopen($file_path, 'r'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming file I/O; WP_Filesystem cannot return a raw stream handle
1020 if (!$handle) {
1021 return false;
1022 }
1023
1024 fseek($handle, -1, SEEK_END);
1025
1026 $result_lines = array();
1027 $line = '';
1028 $line_count = 0;
1029
1030 while (ftell($handle) > 0 && $line_count < $lines) {
1031 $char = fgetc($handle);
1032
1033 if ($char === "\n") {
1034 if (!empty($line)) {
1035 array_unshift($result_lines, strrev($line));
1036 $line = '';
1037 $line_count++;
1038 }
1039 } else {
1040 $line .= $char;
1041 }
1042
1043 fseek($handle, -2, SEEK_CUR);
1044 }
1045
1046 if (!empty($line) && $line_count < $lines) {
1047 array_unshift($result_lines, strrev($line));
1048 }
1049
1050 fclose($handle); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle
1051
1052 return implode("\n", $result_lines);
1053 }
1054 }
1055