Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions includes/Feed/AbstractFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ public function handle_feed_data_request(): void {
\WC_Facebookcommerce_Utils::log_with_debug_mode_enabled( "{$name} feed: Meta is requesting feed file." );

$file_path = $this->feed_writer->get_file_path();
$file = false;

// regenerate if the file doesn't exist using the legacy flow.
if ( ! file_exists( $file_path ) ) {
Expand Down Expand Up @@ -267,8 +268,8 @@ public function handle_feed_data_request(): void {
header( 'Pragma: public' );
header( 'Content-Length:' . filesize( $file_path ) );

// phpcs:ignore
$file = @fopen( $file_path, 'rb' );
// phpcs:ignore -- use php file i/o functions
$file = fopen( $file_path, 'rb' );
if ( ! $file ) {
throw new PluginException( "{$name} feed: Could not open feed file.", 500 );
}
Expand Down Expand Up @@ -297,6 +298,11 @@ public function handle_feed_data_request(): void {
]
);
status_header( $exception->getCode() );
} finally {
if ( $file ) {
// phpcs:ignore -- use php file i/o functions
fclose($file);
}
}
exit;
}
Expand Down
183 changes: 126 additions & 57 deletions includes/Feed/AbstractFeedFileWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public function write_feed_file( array $data ): void {

// Step 3: Rename temporary feed file to final feed file.
$this->promote_temp_file();
} catch ( PluginException $exception ) {
} catch ( \Exception $exception ) {
WC_Facebookcommerce_Utils::log_exception_immediately_to_meta(
$exception,
[
Expand All @@ -135,48 +135,81 @@ public function write_feed_file( array $data ): void {
* Generates the feed file.
*
* @throws PluginException If the directory could not be created.
* @throws \Exception Caught exception is rethrown.
* @since 3.5.0
*/
public function create_feed_directory(): void {
$file_directory = $this->get_file_directory();
$directory_created = wp_mkdir_p( $file_directory );
if ( ! $directory_created ) {
// phpcs:ignore -- Escaping function for translated string not available in this context
throw new PluginException( __( "Could not create feed directory at {$file_directory}", 'facebook-for-woocommerce' ), 500 );
$file_directory = $this->get_file_directory();
try {
$directory_created = wp_mkdir_p( $file_directory );
if ( ! $directory_created ) {
throw new PluginException( "Could not create feed directory at {$file_directory}", 500 );
}
} catch ( \Exception $exception ) {
WC_Facebookcommerce_Utils::log_exception_immediately_to_meta(
$exception,
[
'event' => 'feed_upload',
'event_type' => 'create_feed_directory',
'extra_data' => [
'feed_name' => $this->feed_name,
'file_directory' => $file_directory,
],
]
);
throw $exception;
}
}

/**
* Creates files in the feed directory to prevent directory listing and hotlinking.
*
* @throws \Exception Caught exception is rethrown.
* @since 3.5.0
*/
public function create_files_to_protect_feed_directory(): void {
$feed_directory = trailingslashit( $this->get_file_directory() );
try {
$files = array(
array(
'base' => $feed_directory,
'file' => 'index.html',
'content' => '',
),
array(
'base' => $feed_directory,
'file' => '.htaccess',
'content' => 'deny from all',
),
);

$files = array(
array(
'base' => $feed_directory,
'file' => 'index.html',
'content' => '',
),
array(
'base' => $feed_directory,
'file' => '.htaccess',
'content' => 'deny from all',
),
);

foreach ( $files as $file ) {
$file_path = trailingslashit( $file['base'] ) . $file['file'];
if ( wp_mkdir_p( $file['base'] ) && ! file_exists( $file_path ) ) {
// phpcs:ignore -- use php file i/o functions
$file_handle = @fopen( $file_path, 'w' );
if ( $file_handle ) {
fwrite( $file_handle, $file['content'] ); //phpcs:ignore
fclose( $file_handle ); //phpcs:ignore
foreach ( $files as $file ) {
$file_path = trailingslashit( $file['base'] ) . $file['file'];
if ( wp_mkdir_p( $file['base'] ) && ! file_exists( $file_path ) ) {
// phpcs:ignore -- use php file i/o functions
$file_handle = fopen( $file_path, 'w' );
if ( $file_handle ) {
try {
fwrite( $file_handle, $file['content'] ); //phpcs:ignore
} finally {
fclose( $file_handle ); //phpcs:ignore
}
}
}
}
} catch ( \Exception $exception ) {
WC_Facebookcommerce_Utils::log_exception_immediately_to_meta(
$exception,
[
'event' => 'feed_upload',
'event_type' => 'create_files_to_protect_feed_directory',
'extra_data' => [
'feed_name' => $this->feed_name,
'feed_directory' => $feed_directory,
],
]
);
throw $exception;
}
}

Expand Down Expand Up @@ -242,58 +275,94 @@ public function get_temp_file_name(): string {
* Prepare a fresh empty temporary feed file with the header row.
*
* @throws PluginException We can't open the file or the file is not writable.
* @throws \Exception Caught exception is rethrown.
* @return resource A file pointer resource.
* @since 3.5.0
*/
public function prepare_temporary_feed_file() {
$temp_file_path = $this->get_temp_file_path();
// phpcs:ignore -- use php file i/o functions
$temp_feed_file = @fopen( $temp_file_path, 'w' );

// Check if we can open the temporary feed file.
// phpcs:ignore
if ( false === $temp_feed_file || ! is_writable( $temp_file_path ) ) {
// phpcs:ignore -- Escaping function for translated string not available in this context
throw new PluginException( __( "Could not open file {$temp_file_path} for writing.", 'facebook-for-woocommerce' ), 500 );
}
$temp_feed_file = false;
$file_path = $this->get_file_path();

$file_path = $this->get_file_path();
try {
// phpcs:ignore -- use php file i/o functions
$temp_feed_file = fopen( $temp_file_path, 'w' );

// Check if we will be able to write to the final feed file.
// phpcs:ignore -- use php file i/o functions
if ( file_exists( $file_path ) && ! is_writable( $file_path ) ) {
// phpcs:ignore -- Escaping function for translated string not available in this context
throw new PluginException( __( "Could not open file {$file_path} for writing.", 'facebook-for-woocommerce' ), 500 );
}
// Check if we can open the temporary feed file.
// phpcs:ignore
if ( false === $temp_feed_file || ! is_writable( $temp_file_path ) ) {
throw new PluginException( "Could not open file {$temp_file_path} for writing.", 500 );
}

if ( ! empty( $this->header_row ) ) {
$headers = str_getcsv( $this->header_row );
if ( fputcsv( $temp_feed_file, $headers, $this->delimiter, $this->enclosure, $this->escape_char ) === false ) {
// phpcs:ignore -- Escaping function for translated string not available in this context
throw new PluginException( __( "Failed to write header row to {$temp_file_path}.", 'facebook-for-woocommerce' ), 500 );
// Check if we will be able to write to the final feed file.
// phpcs:ignore -- use php file i/o functions
if ( file_exists( $file_path ) && ! is_writable( $file_path ) ) {
throw new PluginException( "Could not open file {$file_path} for writing.", 500 );
}
}

return $temp_feed_file;
if ( ! empty( $this->header_row ) ) {
$headers = str_getcsv( $this->header_row );
if ( fputcsv( $temp_feed_file, $headers, $this->delimiter, $this->enclosure, $this->escape_char ) === false ) {
throw new PluginException( "Failed to write header row to {$temp_file_path}.", 500 );
}
}

return $temp_feed_file;
} catch ( \Exception $exception ) {
WC_Facebookcommerce_Utils::log_exception_immediately_to_meta(
$exception,
[
'event' => 'feed_upload',
'event_type' => 'prepare_temporary_feed_file',
'extra_data' => [
'feed_name' => $this->feed_name,
'temp_file_path' => $temp_file_path,
'file_path' => $file_path,
],
]
);
if ( $temp_feed_file ) {
// phpcs:ignore -- use php file i/o functions
fclose( $temp_feed_file );
}
throw $exception;
}
}

/**
* Rename temporary feed file into the final feed file.
* This is the last step fo the feed generation procedure.
*
* @throws PluginException If the temporary feed file could not be renamed.
* @throws \Exception Caught exception is rethrown.
* @since 3.5.0
*/
public function promote_temp_file(): void {
$file_path = $this->get_file_path();
$temp_file_path = $this->get_temp_file_path();
if ( ! empty( $temp_file_path ) && ! empty( $file_path ) ) {
// phpcs:ignore -- use php file i/o functions
$renamed = rename( $temp_file_path, $file_path );
if ( empty( $renamed ) ) {
// phpcs:ignore -- Escaping function for translated string not available in this context
throw new PluginException( __( "Could not promote temp file: {$temp_file_path}", 'facebook-for-woocommerce' ), 500 );
$file_path = $this->get_file_path();

try {
if ( ! empty( $temp_file_path ) && ! empty( $file_path ) ) {
// phpcs:ignore -- use php file i/o functions
$renamed = rename( $temp_file_path, $file_path );
if ( empty( $renamed ) ) {
throw new PluginException( "Could not promote temp file: {$temp_file_path}", 500 );
}
}
} catch ( \Exception $exception ) {
WC_Facebookcommerce_Utils::log_exception_immediately_to_meta(
$exception,
[
'event' => 'feed_upload',
'event_type' => 'promote_temp_file',
'extra_data' => [
'feed_name' => $this->feed_name,
'temp_file_path' => $temp_file_path,
'file_path' => $file_path,
],
]
);
throw $exception;
}
}

Expand Down
61 changes: 41 additions & 20 deletions includes/Feed/CsvFeedFileWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace WooCommerce\Facebook\Feed;

use WC_Facebookcommerce_Utils;
use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException;

defined( 'ABSPATH' ) || exit;
Expand All @@ -32,35 +33,55 @@ class CsvFeedFileWriter extends AbstractFeedFileWriter {
*
* @return void
* @throws PluginException If the temporary file cannot be opened or row can't be written.
* @throws \Exception Caught exception is rethrown.
* @since 3.5.0
*/
public function write_temp_feed_file( array $data ): void {
$temp_file_path = $this->get_temp_file_path();
// phpcs:ignore -- use php file i/o functions
$temp_feed_file = fopen( $temp_file_path, 'a' );
if ( false === $temp_feed_file ) {
// phpcs:ignore -- Escaping function for translated string not available in this context
throw new PluginException( __( "Unable to open temporary file {$temp_file_path} for appending.", 'facebook-for-woocommerce' ), 500 );
}
$temp_feed_file = false;
try {
// phpcs:ignore -- use php file i/o functions
$temp_feed_file = fopen( $temp_file_path, 'a' );
if ( false === $temp_feed_file ) {
// phpcs:ignore -- Escaping function for translated string not available in this context
throw new PluginException( "Unable to open temporary file {$temp_file_path} for appending.", 500 );
}

// Convert the header row (CSV string) to an array to use as field accessors.
$accessors = str_getcsv( $this->header_row );
// Convert the header row (CSV string) to an array to use as field accessors.
$accessors = str_getcsv( $this->header_row );

// Process and write each data row.
foreach ( $data as $obj ) {
$row = [];
foreach ( $accessors as $accessor ) {
// Map each field in the row to ensure proper string conversion
$value = $obj[ $accessor ] ?? '';
$row[] = $this->format_field( $value );
// Process and write each data row.
foreach ( $data as $obj ) {
$row = [];
foreach ( $accessors as $accessor ) {
// Map each field in the row to ensure proper string conversion
$value = $obj[ $accessor ] ?? '';
$row[] = $this->format_field( $value );
}
if ( fputcsv( $temp_feed_file, $row, $this->delimiter, $this->enclosure, $this->escape_char ) === false ) {
throw new PluginException( 'Failed to write a CSV data row.', 500 );
}
}
if ( fputcsv( $temp_feed_file, $row, $this->delimiter, $this->enclosure, $this->escape_char ) === false ) {
throw new PluginException( 'Failed to write a CSV data row.', 500 );
} catch ( \Exception $exception ) {
WC_Facebookcommerce_Utils::log_exception_immediately_to_meta(
$exception,
[
'event' => 'feed_upload',
'event_type' => 'write_temp_feed_file',
'extra_data' => [
'feed_name' => $this->feed_name,
'temp_file_path' => $temp_file_path,
'file_type' => 'csv',
],
]
);
throw $exception;
} finally {
if ( $temp_feed_file ) {
// phpcs:ignore -- use php file i/o functions
fclose( $temp_feed_file );
}
}

// phpcs:ignore -- use php file i/o functions
fclose( $temp_feed_file );
}

protected function format_field( $value ) {
Expand Down
Loading