<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2;
use WP_Error;
use WP_Filesystem_Direct;
/**
* FileExport class.
*/
class FileExporter {
/**
* The number of bytes per read while streaming the file.
*
* @const int
*/
private const CHUNK_SIZE = 4 * KB_IN_BYTES;
/**
* The absolute path of the file.
*
* @var string
*/
private $path;
/**
* A name of the file to send to the browser rather than the filename part of the path.
*
* @var string
*/
private $alternate_filename;
/**
* Class FileExporter.
*
* @param string $path The absolute path of the file.
* @param string $alternate_filename Optional. The name of the file to send to the browser rather than the filename
* part of the path.
*/
public function __construct( string $path, string $alternate_filename = '' ) {
global $wp_filesystem;
if ( ! $wp_filesystem instanceof WP_Filesystem_Direct ) {
WP_Filesystem();
}
$this->path = $path;
$this->alternate_filename = $alternate_filename;
}
/**
* Configure PHP and stream the file to the browser.
*
* @return WP_Error|void Only returns something if there is an error.
*/
public function emit_file() {
global $wp_filesystem;
if ( ! $wp_filesystem->is_file( $this->path ) || ! $wp_filesystem->is_readable( $this->path ) ) {
return new WP_Error(
'wc_logs_invalid_file',
__( 'Could not access file.', 'woocommerce' )
);
}
// These configuration tweaks are copied from WC_CSV_Exporter::send_headers().
// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
if ( function_exists( 'gc_enable' ) ) {
gc_enable(); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.gc_enableFound
}
if ( function_exists( 'apache_setenv' ) ) {
@apache_setenv( 'no-gzip', '1' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_apache_setenv
}
@ini_set( 'zlib.output_compression', 'Off' ); // phpcs:ignore WordPress.PHP.IniSet.Risky
@ini_set( 'output_buffering', 'Off' ); // phpcs:ignore WordPress.PHP.IniSet.Risky
@ini_set( 'output_handler', '' ); // phpcs:ignore WordPress.PHP.IniSet.Risky
ignore_user_abort( true );
wc_set_time_limit();
wc_nocache_headers();
// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
$this->send_headers();
$this->send_contents();
die;
}
/**
* Send HTTP headers at the beginning of a file.
*
* Modeled on WC_CSV_Exporter::send_headers().
*
* @return void
*/
private function send_headers(): void {
header( 'Content-Type: text/plain; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=' . $this->get_filename() );
header( 'Pragma: no-cache' );
header( 'Expires: 0' );
}
/**
* Send the contents of the file.
*
* @return void
*/
private function send_contents(): void {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen -- No suitable alternative.
$stream = fopen( $this->path, 'rb' );
while ( is_resource( $stream ) && ! feof( $stream ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fread -- No suitable alternative.
$chunk = fread( $stream, self::CHUNK_SIZE );
if ( is_string( $chunk ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting to file.
echo $chunk;
}
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose -- No suitable alternative.
fclose( $stream );
}
/**
* Get the name of the file that will be sent to the browser.
*
* @return string
*/
private function get_filename(): string {
if ( $this->alternate_filename ) {
return $this->alternate_filename;
}
return basename( $this->path );
}
}