get_recent_logs(self::MAX_ENTRIES); array_unshift($entries, array( 'timestamp' => current_time('Y-m-d H:i:s'), 'type' => in_array($type, array('INFO', 'SUCCESS', 'ERROR', 'WARNING'), true) ? $type : 'INFO', 'message' => substr(sanitize_text_field($message), 0, 2000), )); update_option(self::OPTION_NAME, array_slice($entries, 0, self::MAX_ENTRIES), false); } public function log_conversion_success($id, $format, $saved) { $attachment = get_post($id); $this->log(sprintf('Converted %s to %s. Transfer savings: %s KB', $attachment ? $attachment->post_title : '#' . absint($id), strtoupper($format), $saved), 'SUCCESS'); } public function log_conversion_error($id, $format, $error) { $attachment = get_post($id); $this->log(sprintf('Failed to convert %s to %s. Error: %s', $attachment ? $attachment->post_title : '#' . absint($id), strtoupper($format), $error), 'ERROR'); } public function get_recent_logs($limit = 50) { $entries = get_option(self::OPTION_NAME, array()); return array_slice(is_array($entries) ? $entries : array(), 0, max(0, min(self::MAX_ENTRIES, (int) $limit))); } public function clear_logs() { return delete_option(self::OPTION_NAME); } public function export_logs() { $lines = array(); foreach (array_reverse($this->get_recent_logs(self::MAX_ENTRIES)) as $entry) { $lines[] = sprintf('[%s] [%s] %s', $entry['timestamp'], $entry['type'], $entry['message']); } return implode("\n", $lines); } public function migrate_legacy_logs() { if (!current_user_can('manage_options') || get_option('iwac_private_logs_migrated')) { return; } $base = realpath(wp_upload_dir()['basedir']); if (!$base) { return; } $failed = false; foreach ((array) glob($base . '/iwac-conversion.log*') as $file) { if (!preg_match('/^iwac-conversion\\.log(?:\\.\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2})?$/', basename($file)) || is_link($file) || !is_file($file) || realpath(dirname($file)) !== $base) { continue; } if (!is_readable($file)) { $failed = true; continue; } $legacy = array(); try { $reader = new SplFileObject($file, 'r'); while (!$reader->eof()) { $line = $reader->fgets(); if (preg_match('/^\\[(.*?)\\]\\s\\[(.*?)\\]\\s(.*)$/', trim($line), $match)) { $legacy[] = array('timestamp' => sanitize_text_field($match[1]), 'type' => in_array($match[2], array('INFO', 'SUCCESS', 'ERROR', 'WARNING'), true) ? $match[2] : 'INFO', 'message' => substr(sanitize_text_field($match[3]), 0, 2000)); if (count($legacy) > self::MAX_ENTRIES) { array_shift($legacy); } } } unset($reader); } catch (Throwable $error) { $failed = true; continue; } $merged = array_merge($this->get_recent_logs(self::MAX_ENTRIES), $legacy); $unique = array(); foreach ($merged as $entry) { $unique[md5(wp_json_encode($entry))] = $entry; } $merged = array_values($unique); usort($merged, static function ($a, $b) { return strcmp($b['timestamp'], $a['timestamp']); }); $merged = array_slice($merged, 0, self::MAX_ENTRIES); update_option(self::OPTION_NAME, $merged, false); if (get_option(self::OPTION_NAME, array()) !== $merged) { $failed = true; continue; } // Delete only the plugin's known legacy log names after private storage is verified. wp_delete_file($file); clearstatcache(true, $file); if (file_exists($file)) { $failed = true; } } update_option('iwac_legacy_logs_cleanup_failed', $failed, false); if (!$failed) { update_option('iwac_private_logs_migrated', 1, false); } } public function migration_notice() { if (current_user_can('manage_options') && get_option('iwac_legacy_logs_cleanup_failed')) { echo '
' . esc_html__('Infinico Image Optimizer could not remove old public conversion logs. Check upload-directory permissions and remove iwac-conversion.log and its dated backups after keeping a private copy.', 'infinico-image-optimizer') . '