*/
class Metasync_Error_Logs
{
/**
* The log file path
* @var string
*/
private $log_file_path = null;
/**
* The error message if log cannot be displayed
* @var string
*/
private $error_message = '';
/**
* Maximum log file size in bytes (10MB)
* @var int
*/
private $max_log_size = 10485760; // 10MB
/**
* Show copy button - integrated into preview header
*/
public function show_copy_button()
{
// Button is now shown in the preview header
// Keeping this method for backward compatibility
}
/**
* Show information about the error log file.
* Info is now integrated into the preview header
*/
public function show_info()
{
// Info is now shown in the preview header
// Keeping this method for backward compatibility
}
/**
* Show strong message.
*/
public function show_strong_message($message)
{
?>
get_actual_log_path();
$log_content = $this->get_error_logs(-1); // Get all content to count lines
$lines = explode("\n", $log_content);
$total_lines = count(array_filter($lines, 'strlen')); // Count non-empty lines
// Get last 100 lines for preview
$preview_lines = array_slice($lines, -100);
$preview_content = implode("\n", $preview_lines);
$hidden_lines = max(0, $total_lines - 100);
?>
error_message = '';
// Create the error log file first
$file_path = $this->createErrorLog();
// Store the log file path for use in other methods
$this->log_file_path = $file_path;
// Use the created log file path instead of ini_get('error_log')
$log_file = $file_path;
if (empty($file_path)) {
$this->error_message = 'Unable to create error log file.';
return false;
}
$wp_filesystem = $this->wp_filesystem();
if (is_null($wp_filesystem)) {
$this->error_message = 'WordPress filesystem not available.';
return false;
}
if (!$wp_filesystem->exists($log_file)) {
$this->error_message = 'Error log file does not exist: ' . $log_file;
return false;
}
if (!$wp_filesystem->is_readable($log_file)) {
$this->error_message = 'Error log file is not readable.';
return false;
}
// Error log must be smaller than 100 MB.
$size = $wp_filesystem->size($log_file);
if ($size > 100000000) {
$wp_filesystem->delete($log_file);
$this->error_message = 'The error log cannot be retrieved: Error log file is too large.';
return false;
}
return true;
}
/**
* Get the error message if log cannot be displayed.
* @return string
*/
public function get_error_message()
{
return $this->error_message;
}
/**
* WordPress filesystem for use.
*
* @return string
*/
public function get_error_logs($limit = -1)
{
$wp_filesystem = $this->wp_filesystem();
// Use the actual log file path
$log_file = $this->get_actual_log_path();
if (empty($log_file) || !$wp_filesystem->exists($log_file)) {
return '';
}
$contents = $wp_filesystem->get_contents_array($log_file);
if (-1 === $limit) {
return join('', $contents);
}
return join('', array_slice($contents, -$limit));
}
/**
* WordPress filesystem for use.
*
* @return object
*/
private function wp_filesystem()
{
global $wp_filesystem;
if (empty($wp_filesystem)) {
require_once ABSPATH . '/wp-admin/includes/file.php';
WP_Filesystem();
}
return $wp_filesystem;
}
/**
* Get error log file location.
*
* @return string
*/
private function get_log_path()
{
return ini_get('error_log') != '' ? ini_get('error_log') : '';
}
/**
* Get the actual log file path (either stored or create a new one).
*
* @return string
*/
private function get_actual_log_path()
{
// If we have a stored log file path, use it
if (!empty($this->log_file_path)) {
return $this->log_file_path;
}
// Otherwise, try to get the default log path
$log_file = $this->get_log_path();
// If default path is empty or doesn't exist, create one
if (empty($log_file)) {
$log_file = $this->createErrorLog();
$this->log_file_path = $log_file;
}
return $log_file;
}
/**
* Clear the log.
* @return void
*/
public static function clear()
{
if (ini_get('error_log') === '') return;
$handle = fopen(ini_get('error_log'), 'w');
fclose($handle);
}
/**
* Get human read number of units.
*
* @param int $bytes
* @return string
*/
public function get_human_number(int $bytes)
{
if ($bytes >= 1073741824) {
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
} elseif ($bytes >= 1048576) {
$bytes = number_format($bytes / 1048576, 2) . ' MB';
} elseif ($bytes >= 1024) {
$bytes = number_format($bytes / 1024, 2) . ' KB';
} elseif ($bytes > 1) {
$bytes = $bytes . ' bytes';
} elseif ($bytes == 1) {
$bytes = $bytes . ' byte';
} else {
$bytes = '0 bytes';
}
return $bytes;
}
/**
* Get or create the error log file path
* Points to /metasync_data/plugin_errors.log which is actively written by Log_Manager
*
* @return string|false Log file path or false on failure
*/
public function createErrorLog()
{
// Use the same directory as Log_Manager for consistency
$log_directory = WP_CONTENT_DIR . '/metasync_data';
// Create directory if it doesn't exist
if (!is_dir($log_directory)) {
if (!@mkdir($log_directory, 0755, true)) {
error_log('Metasync_Error_Logs: Failed to create log directory: ' . $log_directory);
return false;
}
}
// Verify directory is writable
if (!is_writable($log_directory)) {
@chmod($log_directory, 0755);
if (!is_writable($log_directory)) {
error_log('Metasync_Error_Logs: Directory not writable: ' . $log_directory);
return false;
}
}
// Point to the actual log file being written by Log_Manager
$log_file = wp_normalize_path($log_directory . '/plugin_errors.log');
// Create file if it doesn't exist
if (!file_exists($log_file)) {
if (@file_put_contents($log_file, '') === false) {
error_log('Metasync_Error_Logs: Failed to create log file: ' . $log_file);
return false;
}
@chmod($log_file, 0644);
}
// Rotate log file if it's too large
$this->rotate_log_if_needed($log_file);
return $log_file;
}
/**
* Rotate log file if it exceeds maximum size
* Keeps the most recent entries and archives old ones
*
* @param string $log_file Path to the log file
* @return bool True if rotation occurred, false otherwise
*/
private function rotate_log_if_needed($log_file)
{
if (!file_exists($log_file)) {
return false;
}
$file_size = filesize($log_file);
// If file is under the limit, no rotation needed
if ($file_size < $this->max_log_size) {
return false;
}
// Create backup of current log with timestamp
$backup_file = $log_file . '.old.' . date('Y-m-d-His');
// Read last 50% of the file to keep recent entries
$wp_filesystem = $this->wp_filesystem();
if (!$wp_filesystem) {
return false;
}
$content = $wp_filesystem->get_contents($log_file);
if ($content === false) {
return false;
}
// Split into lines and keep the most recent half
$lines = explode("\n", $content);
$total_lines = count($lines);
$keep_lines = (int) ($total_lines / 2);
// Keep the most recent lines
$recent_content = implode("\n", array_slice($lines, -$keep_lines));
// Archive old content
@file_put_contents($backup_file, $content);
@chmod($backup_file, 0644);
// Write recent content back to main log
$wp_filesystem->put_contents($log_file, $recent_content, FS_CHMOD_FILE);
// Clean up old backup files (keep only last 3 backups)
$this->cleanup_old_backups(dirname($log_file));
return true;
}
/**
* Clean up old backup log files
* Keeps only the 3 most recent backup files
*
* @param string $directory Directory containing backup files
*/
private function cleanup_old_backups($directory)
{
$backup_files = glob($directory . '/plugin_errors.log.old.*');
if (!$backup_files || count($backup_files) <= 3) {
return;
}
// Sort by modification time (newest first)
usort($backup_files, function($a, $b) {
return filemtime($b) - filemtime($a);
});
// Delete all but the 3 newest
$files_to_delete = array_slice($backup_files, 3);
foreach ($files_to_delete as $file) {
@unlink($file);
}
}
}