PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.20
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.20
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-manager.php

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

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