%s is not supported. Please change it to an absolute path.', '0-day-analytics' ), \esc_html( self::$log_file ) ) ); return self::$last_error; } elseif ( ! file_exists( self::$log_file ) ) { self::$last_error = new \WP_Error( 'error_log_not_exists', sprintf( // translators: the name of the log file. __( 'The log file %s does not exist (maybe empty because there are no errors logged yet).', '0-day-analytics' ), \esc_html( self::$log_file ) ) ); return self::$last_error; } elseif ( ! is_writable( self::$log_file ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable self::$last_error = new \WP_Error( 'error_log_not_writable', sprintf( // translators: the name of the log file. __( 'The log file %s exists, but is not writable. Please check file permissions.', '0-day-analytics' ), \esc_html( self::$log_file ) ) ); return self::$log_file; } elseif ( file_exists( self::$log_file ) && ! is_readable( self::$log_file ) ) { self::$last_error = new \WP_Error( 'error_log_not_accessible', sprintf( // translators: the name of the log file. __( 'The log file %s exists, but is not accessible. Please check file permissions.', '0-day-analytics' ), \esc_html( self::$log_file ) ) ); return self::$last_error; } } return self::$log_file; } /** * Truncates the given file. * * @param string|resource $filename - The name of the file. * * @return void * * @since 1.1.0 */ public static function clear( $filename ) { $filename = self::extract_file_name( $filename ); // Only allow clearing the actual detected error log file. if ( $filename && self::is_same_log_file( $filename ) && \is_writable( $filename ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable $handle = \fopen( $filename, 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen if ( false !== $handle ) { \fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose } } } /** * Returns the file size. * * @param string|resource $filename - The name of the file. * * @return int|false * * @since 1.1.0 */ public static function get_file_size( $filename ) { $filename = self::extract_file_name( $filename ); // Restrict to the detected log file only to avoid information disclosure. if ( ! $filename || ! self::is_same_log_file( $filename ) ) { return false; } return ( file_exists( $filename ) && is_readable( $filename ) ) ? filesize( $filename ) : false; } /** * Returns the modification time of a file. * * @param string|resource $filename - The name of the file. * * @return int|false * * @since 1.1.0 */ public static function get_modification_time( $filename ) { $filename = self::extract_file_name( $filename ); // Restrict to the detected log file only to avoid information disclosure. if ( ! $filename || ! self::is_same_log_file( $filename ) ) { return false; } return ( file_exists( $filename ) && is_readable( $filename ) ) ? filemtime( $filename ) : false; } /** * Tries to extract the string representation of the file. Returns false if it fails or string on success. * * @param string|resource $file - The file to be used as a string representation. * * @return string|bool * * @since 1.1.0 */ public static function extract_file_name( $file ) { $filename = false; if ( \is_resource( $file ) && 'handle' === \get_resource_type( $file ) ) { $meta_data = \stream_get_meta_data( $file ); $filename = $meta_data['uri']; } elseif ( \is_string( $file ) && \file_exists( $file ) && \is_readable( $file ) ) { $filename = $file; } return $filename; } /** * Returns last stored error (if exists) or null. * * @return \WP_Error|null * * @since 1.1.0 */ public static function get_last_error() { return self::$last_error; } /** * Suppress error logging. * * @return void * * @since 1.9.2 */ public static function suppress_error_logging() { if ( null === self::$log_file ) { self::autodetect(); } ini_set( 'log_errors', false ); // phpcs:ignore WordPress.PHP.IniSet.log_errors_Disallowed } /** * Enables error logging. * * @return void * * @since 1.9.2 */ public static function enable_error_logging() { ini_set( 'log_errors', 1 ); // phpcs:ignore WordPress.PHP.IniSet.log_errors_Disallowed if ( ! empty( self::$log_file ) && is_string( self::$log_file ) ) { ini_set( 'error_log', self::$log_file ); // phpcs:ignore WordPress.PHP.IniSet.Risky } } /** * Returns the stored value in the internal class var (name of the error log file). * Returns an empty string if that variable is null or autodetect fails. * * @return string Empty string if autodetect fails or log file is not set. * * @since 1.9.5 */ public static function get_error_log_file(): string { return (string) self::$log_file; } /** * Truncates error log file but keeps the last (settings) errors. * * @return void * * @since 2.8.2 */ public static function truncate_and_keep_errors() { self::suppress_error_logging(); $file_and_path = self::autodetect(); if ( \is_wp_error( $file_and_path ) ) { // Restore logging and abort safely if autodetect failed. self::enable_error_logging(); return; } $dirname = pathinfo( $file_and_path, PATHINFO_DIRNAME ); $real_dirname = realpath( $dirname ); if ( false === $real_dirname ) { $real_dirname = $dirname; } $temp_file = File_Helper::generate_random_file_name() . '.log'; // Ensure safe filename characters. $temp_file = preg_replace( '/[^A-Za-z0-9_.-]/', '_', $temp_file ); $new_log_file = \trailingslashit( $real_dirname ) . $temp_file; // Ensure the temp file exists before setting the handle. if ( ! file_exists( $new_log_file ) ) { touch( $new_log_file ); } Reverse_Line_Reader::set_temp_handle_from_file_path( $new_log_file ); $new_log_file = \trailingslashit( $dirname ) . $temp_file; Reverse_Line_Reader::set_temp_handle_from_file_path( $new_log_file ); $items = Logs_List::get_error_items( true, Settings::get_option( 'keep_error_log_records_truncate' ) ); self::clear( $file_and_path ); File_Helper::remove_empty_lines_low_memory( $new_log_file ); rename( $new_log_file, $file_and_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename Reverse_Line_Reader::set_temp_handle_from_file_path( $new_log_file ); $items = Logs_List::get_error_items( true, Settings::get_option( 'keep_error_log_records_truncate' ) ); self::clear( $file_and_path ); File_Helper::remove_empty_lines_low_memory( $new_log_file ); rename( $new_log_file, $file_and_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename self::enable_error_logging(); } } }