PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.18
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.18
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 / site-error-logs / class-metasync-error-logs.php

class-metasync-error-logs.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.18, at site-error-logs/class-metasync-error-logs.php

420 lines 11.0 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 /**
9 * The site error logs for the plugin.
10 *
11 * @since 1.0.0
12 * @package Metasync
13 * @subpackage Metasync/site-error-logs
14 * @author Engineering Team <[email protected]>
15 */
16 class Metasync_Error_Logs
17 {
18 /**
19 * The log file path
20 * @var string
21 */
22 private $log_file_path = null;
23
24 /**
25 * The error message if log cannot be displayed
26 * @var string
27 */
28 private $error_message = '';
29
30 /**
31 * Maximum log file size in bytes (10MB)
32 * @var int
33 */
34 private $max_log_size = 10485760; // 10MB
35
36 /**
37 * Show copy button - integrated into preview header
38 */
39 public function show_copy_button()
40 {
41 // Button is now shown in the preview header
42 // Keeping this method for backward compatibility
43 }
44
45 /**
46 * Show information about the error log file.
47 * Info is now integrated into the preview header
48 */
49 public function show_info()
50 {
51 // Info is now shown in the preview header
52 // Keeping this method for backward compatibility
53 }
54
55 /**
56 * Show strong message.
57 */
58 public function show_strong_message($message)
59 {
60 ?>
61 <strong class="error-log-cannot-display"><?php echo esc_html($message) ?></strong><br>
62 <?php
63 }
64
65 /**
66 * Show strong message.
67 */
68 public function show_code_html($code)
69 {
70 ?>
71 <code><?php echo esc_html($code); ?></code>
72 <?php
73 }
74
75 /**
76 * Show error log preview with sitemap-style layout
77 */
78 public function show_logs()
79 {
80 $log_file = $this->get_actual_log_path();
81 $log_content = $this->get_error_logs(-1); // Get all content to count lines
82 $lines = explode("\n", $log_content);
83 $total_lines = count(array_filter($lines, 'strlen')); // Count non-empty lines
84
85 // Get last 100 lines for preview
86 $preview_lines = array_slice($lines, -100);
87 $preview_content = implode("\n", $preview_lines);
88 $hidden_lines = max(0, $total_lines - 100);
89 ?>
90 <div class="metasync-sitemap-preview-container" style="margin-top: 20px;">
91 <div class="metasync-sitemap-preview-header" style="display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: var(--dashboard-card-bg); border: 1px solid var(--dashboard-border); border-bottom: none; border-radius: 8px 8px 0 0;">
92 <span class="metasync-preview-label" style="font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace; font-size: 13px; color: var(--dashboard-text-primary); font-weight: 500;">
93 📄 plugin_errors.log
94 <span style="color: var(--dashboard-text-secondary); font-weight: normal; margin-left: 8px;">
95 (<?php echo esc_html($this->get_human_number($this->wp_filesystem()->size($log_file))); ?> • <?php echo esc_html(number_format($total_lines)); ?> lines)
96 </span>
97 </span>
98 <div style="display: flex; gap: 8px;">
99 <button type="button" class="button button-small" id="copy-log-btn" style="display: flex; align-items: center; gap: 4px;">
100 <span class="dashicons dashicons-clipboard" style="font-size: 16px; width: 16px; height: 16px;"></span>
101 Copy to Clipboard
102 </button>
103 <a href="<?php echo esc_url(content_url('metasync_data/plugin_errors.log')); ?>" target="_blank" class="button button-small" style="display: flex; align-items: center; gap: 4px;">
104 <span class="dashicons dashicons-external" style="font-size: 16px; width: 16px; height: 16px;"></span>
105 View Full Log
106 </a>
107 </div>
108 </div>
109 <div class="metasync-sitemap-code-block" style="background: var(--dashboard-card-bg); border: 1px solid var(--dashboard-border); border-radius: 0 0 8px 8px; padding: 20px; overflow: auto; max-height: 500px;">
110 <pre id="error-log-content" style="margin: 0; padding: 0; font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace; font-size: 12px; line-height: 1.6; color: var(--dashboard-text-primary); white-space: pre-wrap; word-wrap: break-word;"><?php
111 if ($hidden_lines > 0) {
112 echo esc_html("... [" . number_format($hidden_lines) . " earlier lines hidden]\n\n");
113 }
114 echo esc_html($preview_content);
115 ?></pre>
116 </div>
117 </div>
118
119 <?php
120 }
121
122 /**
123 * Show message if the log cannot be established.
124 */
125 public function can_show_error_logs()
126 {
127 // Reset error message
128 $this->error_message = '';
129
130 // Create the error log file first
131 $file_path = $this->createErrorLog();
132
133 // Store the log file path for use in other methods
134 $this->log_file_path = $file_path;
135
136 // Use the created log file path instead of ini_get('error_log')
137 $log_file = $file_path;
138
139 if (empty($file_path)) {
140 $this->error_message = 'Unable to create error log file.';
141 return false;
142 }
143
144 $wp_filesystem = $this->wp_filesystem();
145
146 if (is_null($wp_filesystem)) {
147 $this->error_message = 'WordPress filesystem not available.';
148 return false;
149 }
150
151 if (!$wp_filesystem->exists($log_file)) {
152 $this->error_message = 'Error log file does not exist: ' . $log_file;
153 return false;
154 }
155
156 if (!$wp_filesystem->is_readable($log_file)) {
157 $this->error_message = 'Error log file is not readable.';
158 return false;
159 }
160
161 // Error log must be smaller than 100 MB.
162 $size = $wp_filesystem->size($log_file);
163 if ($size > 100000000) {
164 $wp_filesystem->delete($log_file);
165 $this->error_message = 'The error log cannot be retrieved: Error log file is too large.';
166 return false;
167 }
168
169 return true;
170 }
171
172 /**
173 * Get the error message if log cannot be displayed.
174 * @return string
175 */
176 public function get_error_message()
177 {
178 return $this->error_message;
179 }
180
181 /**
182 * WordPress filesystem for use.
183 *
184 * @return string
185 */
186 public function get_error_logs($limit = -1)
187 {
188 $wp_filesystem = $this->wp_filesystem();
189
190 // Use the actual log file path
191 $log_file = $this->get_actual_log_path();
192
193 if (empty($log_file) || !$wp_filesystem->exists($log_file)) {
194 return '';
195 }
196
197 $contents = $wp_filesystem->get_contents_array($log_file);
198
199 if (-1 === $limit) {
200 return join('', $contents);
201 }
202
203 return join('', array_slice($contents, -$limit));
204 }
205
206 /**
207 * WordPress filesystem for use.
208 *
209 * @return object
210 */
211 private function wp_filesystem()
212 {
213 global $wp_filesystem;
214
215 if (empty($wp_filesystem)) {
216 require_once ABSPATH . '/wp-admin/includes/file.php';
217 WP_Filesystem();
218 }
219
220 return $wp_filesystem;
221 }
222
223 /**
224 * Get error log file location.
225 *
226 * @return string
227 */
228 private function get_log_path()
229 {
230 return ini_get('error_log') != '' ? ini_get('error_log') : '';
231 }
232
233 /**
234 * Get the actual log file path (either stored or create a new one).
235 *
236 * @return string
237 */
238 private function get_actual_log_path()
239 {
240 // If we have a stored log file path, use it
241 if (!empty($this->log_file_path)) {
242 return $this->log_file_path;
243 }
244
245 // Otherwise, try to get the default log path
246 $log_file = $this->get_log_path();
247
248 // If default path is empty or doesn't exist, create one
249 if (empty($log_file)) {
250 $log_file = $this->createErrorLog();
251 $this->log_file_path = $log_file;
252 }
253
254 return $log_file;
255 }
256
257 /**
258 * Clear the log.
259 * @return void
260 */
261 public static function clear()
262 {
263 if (ini_get('error_log') === '') return;
264 $handle = fopen(ini_get('error_log'), 'w');
265 fclose($handle);
266 }
267
268 /**
269 * Get human read number of units.
270 *
271 * @param int $bytes
272 * @return string
273 */
274 public function get_human_number(int $bytes)
275 {
276 if ($bytes >= 1073741824) {
277 $bytes = number_format($bytes / 1073741824, 2) . ' GB';
278 } elseif ($bytes >= 1048576) {
279 $bytes = number_format($bytes / 1048576, 2) . ' MB';
280 } elseif ($bytes >= 1024) {
281 $bytes = number_format($bytes / 1024, 2) . ' KB';
282 } elseif ($bytes > 1) {
283 $bytes = $bytes . ' bytes';
284 } elseif ($bytes == 1) {
285 $bytes = $bytes . ' byte';
286 } else {
287 $bytes = '0 bytes';
288 }
289
290 return $bytes;
291 }
292
293 /**
294 * Get or create the error log file path
295 * Points to /metasync_data/plugin_errors.log which is actively written by Log_Manager
296 *
297 * @return string|false Log file path or false on failure
298 */
299 public function createErrorLog()
300 {
301 // Use the same directory as Log_Manager for consistency
302 $log_directory = WP_CONTENT_DIR . '/metasync_data';
303
304 // Create directory if it doesn't exist
305 if (!is_dir($log_directory)) {
306 if (!@mkdir($log_directory, 0755, true)) {
307 error_log('Metasync_Error_Logs: Failed to create log directory: ' . $log_directory);
308 return false;
309 }
310 }
311
312 // Verify directory is writable
313 if (!is_writable($log_directory)) {
314 @chmod($log_directory, 0755);
315 if (!is_writable($log_directory)) {
316 error_log('Metasync_Error_Logs: Directory not writable: ' . $log_directory);
317 return false;
318 }
319 }
320
321 // Point to the actual log file being written by Log_Manager
322 $log_file = wp_normalize_path($log_directory . '/plugin_errors.log');
323
324 // Create file if it doesn't exist
325 if (!file_exists($log_file)) {
326 if (@file_put_contents($log_file, '') === false) {
327 error_log('Metasync_Error_Logs: Failed to create log file: ' . $log_file);
328 return false;
329 }
330 @chmod($log_file, 0644);
331 }
332
333 // Rotate log file if it's too large
334 $this->rotate_log_if_needed($log_file);
335
336 return $log_file;
337 }
338
339 /**
340 * Rotate log file if it exceeds maximum size
341 * Keeps the most recent entries and archives old ones
342 *
343 * @param string $log_file Path to the log file
344 * @return bool True if rotation occurred, false otherwise
345 */
346 private function rotate_log_if_needed($log_file)
347 {
348 if (!file_exists($log_file)) {
349 return false;
350 }
351
352 $file_size = filesize($log_file);
353
354 // If file is under the limit, no rotation needed
355 if ($file_size < $this->max_log_size) {
356 return false;
357 }
358
359 // Create backup of current log with timestamp
360 $backup_file = $log_file . '.old.' . date('Y-m-d-His');
361
362 // Read last 50% of the file to keep recent entries
363 $wp_filesystem = $this->wp_filesystem();
364 if (!$wp_filesystem) {
365 return false;
366 }
367
368 $content = $wp_filesystem->get_contents($log_file);
369 if ($content === false) {
370 return false;
371 }
372
373 // Split into lines and keep the most recent half
374 $lines = explode("\n", $content);
375 $total_lines = count($lines);
376 $keep_lines = (int) ($total_lines / 2);
377
378 // Keep the most recent lines
379 $recent_content = implode("\n", array_slice($lines, -$keep_lines));
380
381 // Archive old content
382 @file_put_contents($backup_file, $content);
383 @chmod($backup_file, 0644);
384
385 // Write recent content back to main log
386 $wp_filesystem->put_contents($log_file, $recent_content, FS_CHMOD_FILE);
387
388 // Clean up old backup files (keep only last 3 backups)
389 $this->cleanup_old_backups(dirname($log_file));
390
391 return true;
392 }
393
394 /**
395 * Clean up old backup log files
396 * Keeps only the 3 most recent backup files
397 *
398 * @param string $directory Directory containing backup files
399 */
400 private function cleanup_old_backups($directory)
401 {
402 $backup_files = glob($directory . '/plugin_errors.log.old.*');
403
404 if (!$backup_files || count($backup_files) <= 3) {
405 return;
406 }
407
408 // Sort by modification time (newest first)
409 usort($backup_files, function($a, $b) {
410 return filemtime($b) - filemtime($a);
411 });
412
413 // Delete all but the 3 newest
414 $files_to_delete = array_slice($backup_files, 3);
415 foreach ($files_to_delete as $file) {
416 @unlink($file);
417 }
418 }
419 }
420