nder $target_string = preg_replace( '/[\x00-\x1F]/', '', $target_string ); // Strip ASCII chars 127 and higher return preg_replace( '/[\x7F-\xFF]/', '', $target_string ); } /** * Helper method to check if the multibyte extension is loaded, which * indicates it's safe to use the mb_*() string methods * * @since 2.2.0 * @return bool */ protected static function multibyte_loaded() { return extension_loaded( 'mbstring' ); } /** Array functions ***************************************************/ /** * Insert the given element after the given key in the array * * Sample usage: * * given * * array( 'item_1' => 'foo', 'item_2' => 'bar' ) * * array_insert_after( $array, 'item_1', array( 'item_1.5' => 'w00t' ) ) * * becomes * * array( 'item_1' => 'foo', 'item_1.5' => 'w00t', 'item_2' => 'bar' ) * * @since 2.2.0 * @param array $input_array Array to insert the given element into. * @param string $insert_key Key to insert given element after. * @param array $element Element to insert into array. * @return array */ public static function array_insert_after( array $input_array, $insert_key, array $element ) { $new_array = []; foreach ( $input_array as $key => $value ) { $new_array[ $key ] = $value; if ( $insert_key === $key ) { foreach ( $element as $k => $v ) { $new_array[ $k ] = $v; } } } return $new_array; } /** Number helper functions *******************************************/ /** * Format a number with 2 decimal points, using a period for the decimal * separator and no thousands separator. * * Commonly used for payment gateways which require amounts in this format. * * @since 3.0.0 * @param float $number * @return string */ public static function number_format( $number ) { return number_format( (float) $number, 2, '.', '' ); } /** WooCommerce helper functions **************************************/ /** * Safely gets a value from $_POST. * * If the expected data is a string also trims it. * * @since 5.5.0 * * @param string $key Posted data key. * @param int|float|array|bool|null|string $default_value Default data type to return (default empty string). * @return int|float|array|bool|null|string Posted data value if key found, or default. */ public static function get_posted_value( $key, $default_value = '' ) { $value = $default_value; //phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST[ $key ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing $sanitized_value = wc_clean( wp_unslash( $_POST[ $key ] ) ); $value = is_string( $sanitized_value ) ? trim( $sanitized_value ) : $sanitized_value; } return $value; } /** * Safely gets a value from $_REQUEST. * * If the expected data is a string also trims it. * * @since 5.5.0 * * @param string $key Posted data key. * @param int|float|array|bool|null|string $default_value Default data type to return (default empty string). * @return int|float|array|bool|null|string Posted data value if key found, or default. */ public static function get_requested_value( $key, $default_value = '' ) { $value = $default_value; //phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST[ $key ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended $sanitized_value = wc_clean( wp_unslash( $_REQUEST[ $key ] ) ); $value = is_string( $sanitized_value ) ? trim( $sanitized_value ) : $sanitized_value; } return $value; } /** * Get the count of notices added, either for all notices (default) or for one * particular notice type specified by $notice_type. * * WC notice functions are not available in the admin * * @since 3.0.2 * @param string $notice_type The name of the notice type - either error, success or notice. [optional] * @return int */ public static function wc_notice_count( $notice_type = '' ) { if ( function_exists( 'wc_notice_count' ) ) { return wc_notice_count( $notice_type ); } return 0; } /** * Add and store a notice. * * WC notice functions are not available in the admin * * @since 3.0.2 * @param string $message The text to display in the notice. * @param string $notice_type The singular name of the notice type - either error, success or notice. [optional] */ public static function wc_add_notice( $message, $notice_type = 'success' ) { if ( function_exists( 'wc_add_notice' ) ) { wc_add_notice( $message, $notice_type ); } } /** * Print a single notice immediately * * WC notice functions are not available in the admin * * @since 3.0.2 * @param string $message The text to display in the notice. * @param string $notice_type The singular name of the notice type - either error, success or notice. [optional] */ public static function wc_print_notice( $message, $notice_type = 'success' ) { if ( function_exists( 'wc_print_notice' ) ) { wc_print_notice( $message, $notice_type ); } } /** * Gets the current WordPress site name. * * This is helpful for retrieving the actual site name instead of the * network name on multisite installations. * * @since 4.6.0 * @return string */ public static function get_site_name() { return ( is_multisite() ) ? get_blog_details()->blogname : get_bloginfo( 'name' ); } /** Misc functions ****************************************************/ /** * Gets the WordPress current screen. * * @see get_current_screen() replacement which is always available, unlike the WordPress core function * * @since 5.4.2 * * @return \WP_Screen|null */ public static function get_current_screen() { global $current_screen; return ! empty( $current_screen ) ? $current_screen : null; } /** * Checks if the current screen matches a specified ID. * * This helps avoiding using the get_current_screen() function which is not always available, * or setting the substitute global $current_screen every time a check needs to be performed. * * @since 5.4.2 * * @param string $id id (or property) to compare * @param string $prop optional property to compare, defaults to screen id * @return bool */ public static function is_current_screen( $id, $prop = 'id' ) { global $current_screen; return isset( $current_screen->$prop ) && $id === $current_screen->$prop; } /** * Determines if the current request is for a WC REST API endpoint. * * @see \WooCommerce::is_rest_api_request() * * @since 5.9.0 * * @return bool */ public static function is_rest_api_request() { if ( is_callable( 'WC' ) && is_callable( [ WC(), 'is_rest_api_request' ] ) ) { return (bool) WC()->is_rest_api_request(); } if ( empty( $_SERVER['REQUEST_URI'] ) || ! function_exists( 'rest_get_url_prefix' ) ) { return false; } $rest_prefix = trailingslashit( rest_get_url_prefix() ); $is_rest_api_request = false !== strpos( wc_clean( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $rest_prefix ); /** Applies WooCommerce core filter */ return (bool) apply_filters( 'woocommerce_is_rest_api_request', $is_rest_api_request ); } } roots as $type => $path ) { if ( 0 === strpos( $frame['file'], $path ) ) { $relative_path = trim( substr( $frame['file'], strlen( $path ) ), DIRECTORY_SEPARATOR ); if ( 'mu-plugin' === $type ) { $info = pathinfo( $relative_path ); if ( '.' === $info['dirname'] ) { $source = "$type-" . $info['filename']; } else { $source = "$type-" . $info['dirname']; } break 2; } $segments = explode( DIRECTORY_SEPARATOR, $relative_path ); if ( is_array( $segments ) ) { $source = "$type-" . reset( $segments ); } break 2; } } } if ( ! $source ) { $source = 'log'; } return sanitize_title( $source ); } /** * Delete all logs from a specific source. * * @param string $source The source of the log entries. * @param bool $quiet Whether to suppress the deletion message. * * @return int The number of files that were deleted. */ public function clear( string $source, bool $quiet = false ): int { $source = File::sanitize_source( $source ); $files = $this->file_controller->get_files( array( 'source' => $source, ) ); if ( is_wp_error( $files ) || count( $files ) < 1 ) { return 0; } $file_ids = array_map( fn( $file ) => $file->get_file_id(), $files ); $deleted = $this->file_controller->delete_files( $file_ids ); if ( $deleted > 0 && ! $quiet ) { $this->handle( time(), 'info', sprintf( esc_html( // translators: %1$s is a number of log files, %2$s is a slug-style name for a file. _n( '%1$s log file from source %2$s was deleted.', '%1$s log files from source %2$s were deleted.', $deleted, 'woocommerce' ) ), number_format_i18n( $deleted ), sprintf( '%s', esc_html( $source ) ) ), array( 'source' => 'wc_logger', 'backtrace' => true, ) ); } return $deleted; } /** * Delete all logs older than a specified timestamp. * * @param int $timestamp All files created before this timestamp will be deleted. * * @return int The number of files that were deleted. */ public function delete_logs_before_timestamp( int $timestamp = 0 ): int { if ( ! $timestamp ) { return 0; } $files = $this->file_controller->get_files( array( 'date_filter' => 'created', 'date_start' => 1, 'date_end' => $timestamp, ) ); if ( is_wp_error( $files ) ) { return 0; } $files = array_filter( $files, function ( $file ) use ( $timestamp ) { /** * Allows preventing an expired log file from being deleted. * * @param bool $delete True to delete the file. * @param File $file The log file object. * @param int $timestamp The expiration threshold. * * @since 8.7.0 */ $delete = apply_filters( 'woocommerce_logger_delete_expired_file', true, $file, $timestamp ); return boolval( $delete ); } ); if ( count( $files ) < 1 ) { return 0; } $file_ids = array_map( fn( $file ) => $file->get_file_id(), $files ); $deleted = $this->file_controller->delete_files( $file_ids ); $retention_days = $this->settings->get_retention_period(); if ( $deleted > 0 ) { $this->handle( time(), 'info', sprintf( esc_html( // translators: %s is a number of log files. _n( '%s expired log file was deleted.', '%s expired log files were deleted.', $deleted, 'woocommerce' ) ), number_format_i18n( $deleted ) ), array( 'source' => 'wc_logger', ) ); } return $deleted; } }