Status/Tools > Logs dropdown. */ private const SOURCE = 'ai-content-by-parallax'; /** * Retrieve WooCommerce logger instance if available. * * @return \WC_Logger|null */ private static function wc_logger() { // Plugin depends on WC, but guard anyway to avoid fatal during bootstrap. if ( function_exists( 'wc_get_logger' ) ) { return wc_get_logger(); } return null; } /** * Build log context array. * * @param array $extra Additional context entries. * @return array */ private static function context( array $extra = array() ): array { return array_merge( array( 'source' => self::SOURCE ), $extra ); } /** * Write a log entry. * * @param string $level emergency|alert|critical|error|warning|notice|info|debug. * @param string $message Log message. * @param mixed $payload Optional payload; will be appended as pretty JSON. */ public static function write_log( string $level, string $message, $payload = null ): void { $logger = self::wc_logger(); // If WC isn't available for some reason, fail soft. if ( ! $logger ) { return; } // if is info level and enable_debug_support is false, do not log. if ( 'info' === $level && ! Settings::get( 'enable_debug_support' ) ) { return; } if ( null !== $payload ) { $pretty = wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); $message .= "\nBEGIN_PAYLOAD\n" . ( false !== $pretty ? $pretty : '[payload_encode_failed]' ) . "\nEND_PAYLOAD"; } // Prefer WC's logger methods. log() is fine too. $logger->log( $level, $message, self::context() ); } /** * Read the most recent log file contents for this source (today’s file). * Note: WC logs are per-day and include a hash in the filename. * * @return string */ public static function read_log(): string { if ( ! class_exists( WC_Log_Handler_File::class ) ) { return ''; } $path = WC_Log_Handler_File::get_log_file_path( self::SOURCE ); if ( ! $path ) { return ''; } $normalized = wp_normalize_path( $path ); $real_path = realpath( $normalized ); if ( false === $real_path ) { return ''; } $real_path = wp_normalize_path( $real_path ); $base_dir = defined( 'WC_LOG_DIR' ) ? (string) WC_LOG_DIR : dirname( $real_path ); $base_dir = wp_normalize_path( $base_dir ); $base_real = realpath( $base_dir ); if ( false !== $base_real ) { $base_dir = wp_normalize_path( $base_real ); } $base_dir = trailingslashit( $base_dir ); // Ensure the resolved log file stays within the WooCommerce log directory. if ( 0 !== strpos( $real_path, $base_dir ) ) { return ''; } if ( ! is_file( $real_path ) || ! is_readable( $real_path ) ) { return ''; } // Cap output size to avoid memory issues. $max_bytes = 262144; // 256KB. if ( ! is_admin() ) { return ''; } // AdminIncludes is responsible for loading WP_Filesystem classes on admin screens. if ( ! class_exists( 'WP_Filesystem_Direct', false ) ) { return ''; } $fs = new \WP_Filesystem_Direct( null ); if ( ! $fs || ! method_exists( $fs, 'get_contents' ) ) { return ''; } $contents = $fs->get_contents( $real_path ); if ( ! is_string( $contents ) || '' === $contents ) { return ''; } // Enforce max bytes even if FS returns more. if ( strlen( $contents ) > $max_bytes ) { $contents = substr( $contents, 0, $max_bytes ); } return esc_html( $contents ); } }