From 94f5d241299277afccf8a2a66262a0feead569d2 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Mon, 3 Feb 2025 10:58:48 -0800 Subject: [PATCH 01/38] Combing both abstract feed and implementation Added abstract feed and promotions feed promotions feed extending abstract feed --- includes/Feed/AbstractFeed.php | 143 +++++++++++++++++++++++++ includes/Promotions/PromotionsFeed.php | 86 +++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 includes/Feed/AbstractFeed.php create mode 100644 includes/Promotions/PromotionsFeed.php diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php new file mode 100644 index 000000000..c806003b2 --- /dev/null +++ b/includes/Feed/AbstractFeed.php @@ -0,0 +1,143 @@ +add_hooks(); + } + + /** + * Adds the necessary hooks for feed generation and data request handling. + */ + private function add_hooks() { + add_action( Heartbeat::HOURLY, $this->schedule_feed_generation() ); + add_action( self::modify_action_name( self::GENERATE_FEED_ACTION ), $this->regenerate_feed() ); + add_action( self::modify_action_name( self::FEED_GEN_COMPLETE_ACTION ), $this->send_request_to_upload_feed() ); + add_action( 'woocommerce_api_' . self::modify_action_name( self::REQUEST_FEED_ACTION ), $this->handle_feed_data_request() ); + } + + /** + * Schedules the recurring feed generation. + * + * This method must be implemented by the concrete feed class, usually by providing a recurring interval + */ + abstract public function schedule_feed_generation(); + + /** + * Regenerates the product feed. + * + * This method is responsible for initiating the regeneration of the product feed. + * The method ensures that the feed is regenerated based on the defined schedule. + */ + abstract public function regenerate_feed(); + + /** + * Trigger the upload flow + * + * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload + */ + abstract public function send_request_to_upload_feed(); + + /** + * Handles the feed data request. + * + * This method must be implemented by the concrete feed class. + */ + abstract public function handle_feed_data_request(); + + /** + * Gets the URL for retrieving the product feed data. + * + * This method must be implemented by the concrete feed class. + * + * @return string The URL for retrieving the product feed data. + */ + abstract public static function get_feed_data_url(): string; + + /** + * Gets the secret value that should be included in the ProductFeed URL. + * + * This method must be implemented by the concrete feed class. + * + * @return string The secret value for the ProductFeed URL. + */ + abstract public static function get_feed_secret(): string; + + /** + * Modifies the action name by appending the data stream name. + * + * @param string $feed_name The base feed name. + * + * @return string The modified action name. + */ + private static function modify_action_name( string $feed_name ): string { + return $feed_name . self::$data_stream_name; + } +} diff --git a/includes/Promotions/PromotionsFeed.php b/includes/Promotions/PromotionsFeed.php new file mode 100644 index 000000000..7f3ba8edd --- /dev/null +++ b/includes/Promotions/PromotionsFeed.php @@ -0,0 +1,86 @@ + Date: Mon, 3 Feb 2025 14:42:38 -0800 Subject: [PATCH 02/38] Add FeedGenerator and PromotionsFeedGenerator --- includes/Feed/FeedGenerator.php | 120 ++++++++++++++++++ .../Promotions/PromotionsFeedGenerator.php | 70 ++++++++++ 2 files changed, 190 insertions(+) create mode 100644 includes/Feed/FeedGenerator.php create mode 100644 includes/Promotions/PromotionsFeedGenerator.php diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php new file mode 100644 index 000000000..5d43d7bee --- /dev/null +++ b/includes/Feed/FeedGenerator.php @@ -0,0 +1,120 @@ +feed_handler = $feed_handler; + } + + /** + * Called before starting the job. + * Override for specific data stream. + */ + protected function handle_start() { + } + + /** + * Called after the finishing the job. + * Override for specific data stream. + */ + protected function handle_end() { + } + + /** + * Get a set of items for the batch. + * + * NOTE: when using an OFFSET based query to retrieve items it's recommended to order by the item ID while + * ASCENDING. This is so that any newly added items will not disrupt the query offset. + * Override with your custom SQL logic. + * + * @param int $batch_number The batch number increments for each new batch in the job cycle. + * @param array $args The args for the job. + * + * @throws Exception On error. The failure will be logged by Action Scheduler and the job chain will stop. + */ + protected function get_items_for_batch( int $batch_number, array $args ): array { + return array(); + } + + /** + * Processes a batch of items. + * + * @param array $items The items of the current batch, probably compiled as an object. + * @param array $args The args for the job. + * + * @throws Exception On error. The failure will be logged by Action Scheduler and the job chain will stop. + * @since 1.1.0 + */ + protected function process_items( array $items, array $args ) { + } + + /** + * The single item processing logic. Might not need if only using the whole batch. + * + * @param object $item the singular item to process. This method might not be used but needed to extend parent. + * @param array $args the args for the job. + */ + protected function process_item( $item, array $args ) { + } + + /** + * Get the name/slug of the job. + * Ex. generate_product_feed + * + * @return string + */ + public function get_name(): string { + return ''; + } + + /** + * Get the name/slug of the plugin that owns the job. + * + * @return string + */ + public function get_plugin_name(): string { + return WC_Facebookcommerce::PLUGIN_ID; + } + + /** + * Get the job's batch size. + * + * @return int + */ + protected function get_batch_size(): int { + return - 1; + } +} diff --git a/includes/Promotions/PromotionsFeedGenerator.php b/includes/Promotions/PromotionsFeedGenerator.php new file mode 100644 index 000000000..8ff4a23f8 --- /dev/null +++ b/includes/Promotions/PromotionsFeedGenerator.php @@ -0,0 +1,70 @@ + Date: Tue, 4 Feb 2025 12:57:13 -0800 Subject: [PATCH 03/38] Add FeedGenerator and PromotionsFeedGenerator. Add to Job Manager --- includes/Feed/FeedGenerator.php | 5 ++- includes/Feed/FeedGeneratorFactory.php | 48 ++++++++++++++++++++++++++ includes/Jobs/JobManager.php | 10 ++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 includes/Feed/FeedGeneratorFactory.php diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index 5d43d7bee..08eacf679 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -32,11 +32,10 @@ class FeedGenerator extends AbstractChainedJob { * FeedGenerator constructor. * * @param ActionSchedulerInterface $action_scheduler The action scheduler instance. - * @param WC_Facebook_Product_Feed $feed_handler The feed handler instance. */ - public function __construct( ActionSchedulerInterface $action_scheduler, WC_Facebook_Product_Feed $feed_handler ) { + public function __construct( ActionSchedulerInterface $action_scheduler ) { parent::__construct( $action_scheduler ); - $this->feed_handler = $feed_handler; + $this->feed_handler = new WC_Facebook_Product_Feed(); } /** diff --git a/includes/Feed/FeedGeneratorFactory.php b/includes/Feed/FeedGeneratorFactory.php new file mode 100644 index 000000000..cfbec5704 --- /dev/null +++ b/includes/Feed/FeedGeneratorFactory.php @@ -0,0 +1,48 @@ +init(); + $this->feed_generators[ get_class( $promotions_feed_generator ) ] = $promotions_feed_generator; + } + + /** + * Get the feed generator instance. + * + * @param string $feed_gen_class_name The class name of the feed generator. + * + * @return \WooCommerce\Facebook\Feed\FeedGenerator + */ + public function get_feed_generator( string $feed_gen_class_name ): FeedGenerator { + return $this->feed_generators[ $feed_gen_class_name ]; + } +} diff --git a/includes/Jobs/JobManager.php b/includes/Jobs/JobManager.php index a15bba36e..4cfbaa30f 100644 --- a/includes/Jobs/JobManager.php +++ b/includes/Jobs/JobManager.php @@ -3,6 +3,7 @@ namespace WooCommerce\Facebook\Jobs; use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionScheduler; +use WooCommerce\Facebook\Feed\FeedGeneratorFactory; defined( 'ABSPATH' ) || exit; @@ -33,6 +34,12 @@ class JobManager { */ public $delete_all_products; + /** + * Publicly accessible instance to get the correct generator + * @var FeedGeneratorFactory + */ + public FeedGeneratorFactory $generator_factory; + /** * Instantiate and init all jobs for the plugin. */ @@ -42,6 +49,9 @@ public function init() { $this->generate_product_feed_job = new GenerateProductFeed( $action_scheduler_proxy ); $this->generate_product_feed_job->init(); + // Creates all the feed generators except for product + $this->generator_factory = new FeedGeneratorFactory( $action_scheduler_proxy ); + $this->cleanup_skyverge_job_options = new CleanupSkyvergeFrameworkJobOptions(); $this->cleanup_skyverge_job_options->init(); From 70d6980ad74bc5aa7a36f0b38d74d52455c15a6a Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Wed, 5 Feb 2025 09:19:43 -0800 Subject: [PATCH 04/38] implement promotions regenerate feed using generator factory --- includes/Promotions/PromotionsFeed.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/includes/Promotions/PromotionsFeed.php b/includes/Promotions/PromotionsFeed.php index 7f3ba8edd..05c8f5f10 100644 --- a/includes/Promotions/PromotionsFeed.php +++ b/includes/Promotions/PromotionsFeed.php @@ -39,7 +39,15 @@ public function schedule_feed_generation() { * The method ensures that the feed is regenerated based on the defined schedule. */ public function regenerate_feed() { - // TODO: Implement regenerate_feed() method. + // Maybe use new ( experimental ), feed generation framework. + if ( facebook_for_woocommerce()->get_integration()->is_new_style_feed_generation_enabled() ) { + $generate_factory = facebook_for_woocommerce()->job_manager->generator_factory; + $generator = $generate_factory->get_feed_generator( 'PromotionsFeedGenerator' ); + $generator->queue_start(); + } else { + $feed_handler = new \WC_Facebook_Product_Feed(); + $feed_handler->generate_feed(); + } } /** From ff1cf9ab38954337d342dcaf69471ca9d8c03454 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Wed, 5 Feb 2025 14:51:08 -0800 Subject: [PATCH 05/38] Added feed handlers and feed writers --- includes/Feed/CsvFeedFileWriter.php | 108 ++++++++++++++++++ includes/Feed/FeedFileWriter.php | 71 ++++++++++++ includes/Feed/FeedHandler.php | 38 ++++++ includes/Promotions/PromotionsFeedHandler.php | 75 ++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 includes/Feed/CsvFeedFileWriter.php create mode 100644 includes/Feed/FeedFileWriter.php create mode 100644 includes/Feed/FeedHandler.php create mode 100644 includes/Promotions/PromotionsFeedHandler.php diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php new file mode 100644 index 000000000..68e89ee5e --- /dev/null +++ b/includes/Feed/CsvFeedFileWriter.php @@ -0,0 +1,108 @@ +feed_name = $feed_name; + } + + /** + * Write the feed file. + * + * @return void + */ + public function write_feed_file() { + // TODO: Implement write_feed_file() method. + } + + /** + * Creates files in the catalog feed directory to prevent directory listing and hotlinking. + * + * @since 1.11.0 + */ + public function create_files_to_protect_product_feed_directory() { + } + + /** + * Gets the feed file path of given feed. + * + * @return string + * @since 1.11.0 + */ + public function get_file_path(): string { + return ''; + } + + + /** + * Gets the temporary feed file path. + * + * @return string + * @since 1.11.3 + */ + public function get_temp_file_path(): string { + return ''; + } + + /** + * Gets the feed file directory. + * + * @return string + * @since 1.11.0 + */ + public function get_file_directory(): string { + return ''; + } + + + /** + * Gets the feed file name. + * + * @return string + * @since 1.11.0 + */ + public function get_file_name(): string { + return ''; + } + + /** + * Gets the temporary feed file name. + * + * @param string $secret The secret used to generate the file name. + * + * @return string + * @since 1.11.3 + */ + public function get_temp_file_name( string $secret ): string { + return $secret; + } +} diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php new file mode 100644 index 000000000..d44ad5c2a --- /dev/null +++ b/includes/Feed/FeedFileWriter.php @@ -0,0 +1,71 @@ +feed_writer = $feed_writer; + } + + /** + * Generate the feed file. + * + * This method is responsible for generating a feed file. + */ + public function generate_feed_file() { + // TODO: Implement generate_feed_file() method. + } + + /** + * Get the feed upload status. + * + * @param array $settings the settings of the facebook integration. + * + * @return string the status of the feed upload. + */ + public function get_feed_upload_status( array $settings ): string { + // TODO: Implement get_feed_upload_status() method. + return ''; + } + + /** + * Get the item ids to sync for the feed writer. + * + * @return array + */ + public function get_item_ids_to_sync(): array { + // TODO: Implement get_item_ids_to_sync() method. + return array(); + } +} From 4bba8ade8dde645c41200032ccdd5470d80afc5e Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Wed, 5 Feb 2025 16:35:49 -0800 Subject: [PATCH 06/38] Add FeedFileWriter implementation and uses; add abstract class FeedInfoForTracker that will need feed_id and upload_id from the Feed class --- includes/Feed/AbstractFeed.php | 38 ++++++++++++++++++--- includes/Feed/FeedInfoToTracker.php | 46 ++++++++++++++++++++++++++ includes/Promotions/PromotionsFeed.php | 1 - 3 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 includes/Feed/FeedInfoToTracker.php diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index c806003b2..8aa801ac7 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -38,6 +38,19 @@ abstract class AbstractFeed { /** The feed name for creating a new feed by this plugin. Modify in extending class. */ const FEED_NAME = ' by Facebook for WooCommerce plugin. DO NOT DELETE.'; + /** + * The feed ID for the given feed. + * + * @var string + */ + protected string $feed_id; + + /** + * The upload ID for the given feed. + * + * @var string + */ + protected string $upload_id; /** * The name of the data stream to be synced via this feed. @@ -56,10 +69,9 @@ abstract class AbstractFeed { /** * The feed handler instance for the given feed. * - * @var \WC_Facebook_Product_Feed - * Todo: replace with generic feed_handler class + * @var FeedHandler */ - protected \WC_Facebook_Product_Feed $feed_handler; + protected FeedHandler $feed_handler; /** * Constructor. @@ -112,6 +124,24 @@ abstract public function send_request_to_upload_feed(); */ abstract public function handle_feed_data_request(); + /** + * Feed id to be used in upload + * + * @return string + */ + public function get_feed_id(): string { + return $this->feed_id; + } + + /** + * Upload id to be used in upload + * + * @return string + */ + public function get_upload_id(): string { + return $this->upload_id; + } + /** * Gets the URL for retrieving the product feed data. * @@ -137,7 +167,7 @@ abstract public static function get_feed_secret(): string; * * @return string The modified action name. */ - private static function modify_action_name( string $feed_name ): string { + protected static function modify_action_name( string $feed_name ): string { return $feed_name . self::$data_stream_name; } } diff --git a/includes/Feed/FeedInfoToTracker.php b/includes/Feed/FeedInfoToTracker.php new file mode 100644 index 000000000..93c161f75 --- /dev/null +++ b/includes/Feed/FeedInfoToTracker.php @@ -0,0 +1,46 @@ +get_data_source_feed_tracker_info(); + facebook_for_woocommerce()->get_tracker()->track_facebook_feed_config( $info ); + } catch ( Exception $error ) { + facebook_for_woocommerce()->log( 'Unable to detect valid feed configuration: ' . $error->getMessage() ); + } + } + + /** + * Get data needed to set transient in the global Tracker class. + */ + abstract protected function get_data_source_feed_tracker_info(); +} diff --git a/includes/Promotions/PromotionsFeed.php b/includes/Promotions/PromotionsFeed.php index 05c8f5f10..f291f677f 100644 --- a/includes/Promotions/PromotionsFeed.php +++ b/includes/Promotions/PromotionsFeed.php @@ -22,7 +22,6 @@ * @since 1.11.0 */ class PromotionsFeed extends AbstractFeed { - /** * Schedules the recurring feed generation. * From 793332245dc0da318360f51b58dd8fbca53446c8 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Thu, 6 Feb 2025 10:29:41 -0800 Subject: [PATCH 07/38] constructor for promotions feed; csv feed file writer implements interface --- includes/Feed/CsvFeedFileWriter.php | 2 +- includes/Promotions/PromotionsFeed.php | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index 68e89ee5e..c67b2f767 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -18,7 +18,7 @@ * To be used by the feed handler to write its updates to its feed file. * To be used by any feed handler whose feed requires a csv file. */ -class CsvFeedFileWriter { +class CsvFeedFileWriter implements FeedFileWriter { /** * Use the feed name to distinguish which folder to write to. * diff --git a/includes/Promotions/PromotionsFeed.php b/includes/Promotions/PromotionsFeed.php index f291f677f..d97737451 100644 --- a/includes/Promotions/PromotionsFeed.php +++ b/includes/Promotions/PromotionsFeed.php @@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit; use WooCommerce\Facebook\Feed\AbstractFeed; +use WooCommerce\Facebook\Feed\CsvFeedFileWriter; /** * Promotions Feed class @@ -22,6 +23,18 @@ * @since 1.11.0 */ class PromotionsFeed extends AbstractFeed { + + /** + * Constructor. + */ + public function __construct() { + $data_stream_name = 'Promotions'; + $generator_factory = facebook_for_woocommerce()->job_manager->generator_factory; + $this->feed_generator = $generator_factory->get_feed_generator( 'PromotionsFeedGenerator' ); + $this->feed_handler = new PromotionsFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); + parent::__construct( $data_stream_name ); + } + /** * Schedules the recurring feed generation. * @@ -40,12 +53,9 @@ public function schedule_feed_generation() { public function regenerate_feed() { // Maybe use new ( experimental ), feed generation framework. if ( facebook_for_woocommerce()->get_integration()->is_new_style_feed_generation_enabled() ) { - $generate_factory = facebook_for_woocommerce()->job_manager->generator_factory; - $generator = $generate_factory->get_feed_generator( 'PromotionsFeedGenerator' ); - $generator->queue_start(); + $this->feed_generator->queue_start(); } else { - $feed_handler = new \WC_Facebook_Product_Feed(); - $feed_handler->generate_feed(); + $this->feed_handler->generate_feed_file(); } } From 9315b8046565c9c5ebc77fdea324348595f865a1 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Thu, 6 Feb 2025 11:15:04 -0800 Subject: [PATCH 08/38] FeedType Const; Use FeedHandler in FeedGenerator constructor and update factory to reflect --- includes/Feed/FeedGenerator.php | 9 ++++----- includes/Feed/FeedType.php | 21 +++++++++++++++++++++ includes/Promotions/PromotionsFeed.php | 3 ++- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 includes/Feed/FeedType.php diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index 08eacf679..0c4f3ebc3 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -20,22 +20,21 @@ * @since 2.5.0 */ class FeedGenerator extends AbstractChainedJob { - // TODO: replace with generic feed_handler class. /** * The feed handler instance for the given feed. * - * @var WC_Facebook_Product_Feed. + * @var FeedHandler */ - protected WC_Facebook_Product_Feed $feed_handler; + protected FeedHandler $feed_handler; /** * FeedGenerator constructor. * * @param ActionSchedulerInterface $action_scheduler The action scheduler instance. */ - public function __construct( ActionSchedulerInterface $action_scheduler ) { + public function __construct( ActionSchedulerInterface $action_scheduler, FeedHandler $feed_handler ) { parent::__construct( $action_scheduler ); - $this->feed_handler = new WC_Facebook_Product_Feed(); + $this->feed_handler = $feed_handler; } /** diff --git a/includes/Feed/FeedType.php b/includes/Feed/FeedType.php new file mode 100644 index 000000000..b1c1f4177 --- /dev/null +++ b/includes/Feed/FeedType.php @@ -0,0 +1,21 @@ +job_manager->generator_factory; $this->feed_generator = $generator_factory->get_feed_generator( 'PromotionsFeedGenerator' ); $this->feed_handler = new PromotionsFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); From 5e5503e3e73b053ee51d6473bd67a5da10fd3e57 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Thu, 6 Feb 2025 11:35:30 -0800 Subject: [PATCH 09/38] use new feed gen constructor, feed type const in feed constructor --- includes/Feed/FeedGeneratorFactory.php | 9 +++++++-- includes/Promotions/PromotionsFeed.php | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/includes/Feed/FeedGeneratorFactory.php b/includes/Feed/FeedGeneratorFactory.php index cfbec5704..8e2ea99d3 100644 --- a/includes/Feed/FeedGeneratorFactory.php +++ b/includes/Feed/FeedGeneratorFactory.php @@ -5,7 +5,10 @@ defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionScheduler; +use WooCommerce\Facebook\Feed\FeedType; +use WooCommerce\Facebook\Feed\CsvFeedFileWriter; use WooCommerce\Facebook\Promotions\PromotionsFeedGenerator; +use WooCommerce\Facebook\Promotions\PromotionsFeedHandler; /** * Class FeedGeneratorFactory @@ -30,9 +33,11 @@ class FeedGeneratorFactory { * @param ActionScheduler $scheduler The action scheduler instance. */ public function __construct( ActionScheduler $scheduler ) { - $promotions_feed_generator = new PromotionsFeedGenerator( $scheduler ); + $feed_writer = new CsvFeedFileWriter( FeedType::PROMOTIONS ); + $feed_handler = new PromotionsFeedHandler( $feed_writer ); + $promotions_feed_generator = new PromotionsFeedGenerator( $scheduler, $feed_handler ); $promotions_feed_generator->init(); - $this->feed_generators[ get_class( $promotions_feed_generator ) ] = $promotions_feed_generator; + $this->feed_generators[ FeedType::PROMOTIONS ] = $promotions_feed_generator; } /** diff --git a/includes/Promotions/PromotionsFeed.php b/includes/Promotions/PromotionsFeed.php index ef3671c74..6417071cd 100644 --- a/includes/Promotions/PromotionsFeed.php +++ b/includes/Promotions/PromotionsFeed.php @@ -31,7 +31,7 @@ class PromotionsFeed extends AbstractFeed { public function __construct() { $data_stream_name = FeedType::PROMOTIONS; $generator_factory = facebook_for_woocommerce()->job_manager->generator_factory; - $this->feed_generator = $generator_factory->get_feed_generator( 'PromotionsFeedGenerator' ); + $this->feed_generator = $generator_factory->get_feed_generator( $data_stream_name ); $this->feed_handler = new PromotionsFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); parent::__construct( $data_stream_name ); } From a4118338213d5a4d7bbf8deaaab7891d39f1a5c3 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Thu, 6 Feb 2025 15:04:33 -0800 Subject: [PATCH 10/38] change up generator factory --- includes/Feed/FeedGenerator.php | 1 - includes/Feed/FeedGeneratorFactory.php | 12 +++++++----- includes/Promotions/PromotionsFeed.php | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index 0c4f3ebc3..c77c047d3 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -6,7 +6,6 @@ use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionSchedulerInterface; use Exception; -use WC_Facebook_Product_Feed; use WC_Facebookcommerce; use WooCommerce\Facebook\Jobs\AbstractChainedJob; diff --git a/includes/Feed/FeedGeneratorFactory.php b/includes/Feed/FeedGeneratorFactory.php index 8e2ea99d3..fa16eee20 100644 --- a/includes/Feed/FeedGeneratorFactory.php +++ b/includes/Feed/FeedGeneratorFactory.php @@ -5,8 +5,6 @@ defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionScheduler; -use WooCommerce\Facebook\Feed\FeedType; -use WooCommerce\Facebook\Feed\CsvFeedFileWriter; use WooCommerce\Facebook\Promotions\PromotionsFeedGenerator; use WooCommerce\Facebook\Promotions\PromotionsFeedHandler; @@ -33,11 +31,15 @@ class FeedGeneratorFactory { * @param ActionScheduler $scheduler The action scheduler instance. */ public function __construct( ActionScheduler $scheduler ) { - $feed_writer = new CsvFeedFileWriter( FeedType::PROMOTIONS ); + // Will refactor this as more feeds are added. + $data_stream_name = FeedType::PROMOTIONS; + $feed_writer = new CsvFeedFileWriter( $data_stream_name ); + $feed_handler = new PromotionsFeedHandler( $feed_writer ); $promotions_feed_generator = new PromotionsFeedGenerator( $scheduler, $feed_handler ); + $promotions_feed_generator->init(); - $this->feed_generators[ FeedType::PROMOTIONS ] = $promotions_feed_generator; + $this->feed_generators[ $data_stream_name ] = $promotions_feed_generator; } /** @@ -45,7 +47,7 @@ public function __construct( ActionScheduler $scheduler ) { * * @param string $feed_gen_class_name The class name of the feed generator. * - * @return \WooCommerce\Facebook\Feed\FeedGenerator + * @return FeedGenerator */ public function get_feed_generator( string $feed_gen_class_name ): FeedGenerator { return $this->feed_generators[ $feed_gen_class_name ]; diff --git a/includes/Promotions/PromotionsFeed.php b/includes/Promotions/PromotionsFeed.php index 6417071cd..cffbdbef9 100644 --- a/includes/Promotions/PromotionsFeed.php +++ b/includes/Promotions/PromotionsFeed.php @@ -29,9 +29,9 @@ class PromotionsFeed extends AbstractFeed { * Constructor. */ public function __construct() { - $data_stream_name = FeedType::PROMOTIONS; + $data_stream_name = FeedType::PROMOTIONS; $generator_factory = facebook_for_woocommerce()->job_manager->generator_factory; - $this->feed_generator = $generator_factory->get_feed_generator( $data_stream_name ); + $this->feed_generator = $generator_factory->get_feed_generator( $data_stream_name ); $this->feed_handler = new PromotionsFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); parent::__construct( $data_stream_name ); } From f208231a6a572673493dd144108f6a934090532c Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Thu, 6 Feb 2025 15:14:43 -0800 Subject: [PATCH 11/38] use feed info tracker --- includes/Feed/AbstractFeed.php | 7 +++++++ includes/Promotions/PromotionsFeed.php | 3 +++ .../Promotions/PromotionsFeedInfoToTracker.php | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 includes/Promotions/PromotionsFeedInfoToTracker.php diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 8aa801ac7..36967c8da 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -73,6 +73,13 @@ abstract class AbstractFeed { */ protected FeedHandler $feed_handler; + /** + * The feed info to tracker instance for the given feed. + * + * @var FeedInfoToTracker + */ + protected FeedInfoToTracker $feed_info_to_tracker; + /** * Constructor. * diff --git a/includes/Promotions/PromotionsFeed.php b/includes/Promotions/PromotionsFeed.php index cffbdbef9..2c3163688 100644 --- a/includes/Promotions/PromotionsFeed.php +++ b/includes/Promotions/PromotionsFeed.php @@ -14,6 +14,7 @@ use WooCommerce\Facebook\Feed\AbstractFeed; use WooCommerce\Facebook\Feed\CsvFeedFileWriter; use WooCommerce\Facebook\Feed\FeedType; +use WooCommerce\Facebook\Utilities\Heartbeat; /** * Promotions Feed class @@ -33,6 +34,8 @@ public function __construct() { $generator_factory = facebook_for_woocommerce()->job_manager->generator_factory; $this->feed_generator = $generator_factory->get_feed_generator( $data_stream_name ); $this->feed_handler = new PromotionsFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); + // The daily heartbeat is guess for now, can change as needed. + $this->feed_info_to_tracker = new PromotionsFeedInfoToTracker( Heartbeat::DAILY ); parent::__construct( $data_stream_name ); } diff --git a/includes/Promotions/PromotionsFeedInfoToTracker.php b/includes/Promotions/PromotionsFeedInfoToTracker.php new file mode 100644 index 000000000..c4f4046d2 --- /dev/null +++ b/includes/Promotions/PromotionsFeedInfoToTracker.php @@ -0,0 +1,18 @@ + Date: Thu, 6 Feb 2025 15:55:36 -0800 Subject: [PATCH 12/38] manager and factory classes set up --- class-wc-facebookcommerce.php | 4 ++- includes/Feed/FeedFactory.php | 32 ++++++++++++++++++++ includes/Feed/FeedGeneratorFactory.php | 14 ++++----- includes/Feed/FeedManager.php | 42 ++++++++++++++++++++++++++ includes/Feed/FeedType.php | 27 ++++++++++++++++- 5 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 includes/Feed/FeedFactory.php create mode 100644 includes/Feed/FeedManager.php diff --git a/class-wc-facebookcommerce.php b/class-wc-facebookcommerce.php index 4362137d2..698c8eb1d 100644 --- a/class-wc-facebookcommerce.php +++ b/class-wc-facebookcommerce.php @@ -69,6 +69,9 @@ class WC_Facebookcommerce extends WooCommerce\Facebook\Framework\Plugin { /** @var WooCommerce\Facebook\Products\Feed product feed handler */ private $product_feed; + /** @var WooCommerce\Facebook\Feed\FeedManager Entrypoint and creates all other feeds */ + private $feed_manager; + /** @var Background_Handle_Virtual_Products_Variations instance */ protected $background_handle_virtual_products_variations; @@ -187,7 +190,6 @@ public function init() { $this->heartbeat = new Heartbeat( WC()->queue() ); $this->heartbeat->init(); - $this->checkout = new WooCommerce\Facebook\Checkout(); $this->product_feed = new WooCommerce\Facebook\Products\Feed(); $this->products_stock_handler = new WooCommerce\Facebook\Products\Stock(); $this->products_sync_handler = new WooCommerce\Facebook\Products\Sync(); diff --git a/includes/Feed/FeedFactory.php b/includes/Feed/FeedFactory.php new file mode 100644 index 000000000..a186379fb --- /dev/null +++ b/includes/Feed/FeedFactory.php @@ -0,0 +1,32 @@ +init(); - $this->feed_generators[ $data_stream_name ] = $promotions_feed_generator; + $promotions_feed_generator->init(); + $this->feed_generators[ $data_stream_name ] = $promotions_feed_generator; + } } /** diff --git a/includes/Feed/FeedManager.php b/includes/Feed/FeedManager.php new file mode 100644 index 000000000..fa211c59e --- /dev/null +++ b/includes/Feed/FeedManager.php @@ -0,0 +1,42 @@ + The list of feed types as named strings. + */ + private array $feed_types; + + /** + * The map of feed types to their instances. + * + * @var array The map of feed types to their instances. + */ + private array $feed_instances = array(); + + /** + * FeedManager constructor. + * Instantiates all the registered feed types and keeps in map. + */ + public function __construct() { + $this->feed_factory = new FeedFactory(); + $this->feed_types = FeedType::get_feed_types(); + foreach ( $this->feed_types as $feed_type ) { + $this->feed_instances[ $feed_type ] = $this->feed_factory->create_feed( $feed_type ); + } + } +} diff --git a/includes/Feed/FeedType.php b/includes/Feed/FeedType.php index b1c1f4177..1af002586 100644 --- a/includes/Feed/FeedType.php +++ b/includes/Feed/FeedType.php @@ -17,5 +17,30 @@ */ class FeedType { const PROMOTIONS = 'promotions'; - const PRODUCTS = 'products'; + + /** + * Get the list of feed types. + * + * @return array + */ + public static function get_feed_types(): array { + return array( self::PROMOTIONS ); + } + + /** + * Get the feed file writer for the given data stream name. + * + * @param string $data_stream_name The name of the data stream. + * + * @return FeedFileWriter + * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType. + */ + public static function get_feed_file_writer( string $data_stream_name ): FeedFileWriter { + switch ( $data_stream_name ) { + case self::PROMOTIONS: + return new CsvFeedFileWriter( $data_stream_name ); + default: + throw new \InvalidArgumentException( 'Invalid data stream name' ); + } + } } From aa1b642782c9f9bd16162d85b6bb93d3034c4215 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Fri, 7 Feb 2025 16:30:47 -0800 Subject: [PATCH 13/38] Request and response for Common Feed Upload Create and Read --- .../API/CommonFeedUploads/Create/Request.php | 26 +++++++++++++++++++ .../API/CommonFeedUploads/Create/Response.php | 16 ++++++++++++ .../API/CommonFeedUploads/Read/Request.php | 24 +++++++++++++++++ .../API/CommonFeedUploads/Read/Response.php | 16 ++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 includes/API/CommonFeedUploads/Create/Request.php create mode 100644 includes/API/CommonFeedUploads/Create/Response.php create mode 100644 includes/API/CommonFeedUploads/Read/Request.php create mode 100644 includes/API/CommonFeedUploads/Read/Response.php diff --git a/includes/API/CommonFeedUploads/Create/Request.php b/includes/API/CommonFeedUploads/Create/Request.php new file mode 100644 index 000000000..f14597514 --- /dev/null +++ b/includes/API/CommonFeedUploads/Create/Request.php @@ -0,0 +1,26 @@ + Product Feed Upload > Create Graph Api. + * + * @link https://developers.facebook.com/docs/marketing-api/reference/product-feed/uploads/#Creating + * @property-read array $data Facebook Product Feeds Upload. + */ +class Response extends ApiResponse {} diff --git a/includes/API/CommonFeedUploads/Read/Request.php b/includes/API/CommonFeedUploads/Read/Request.php new file mode 100644 index 000000000..85f93a30a --- /dev/null +++ b/includes/API/CommonFeedUploads/Read/Request.php @@ -0,0 +1,24 @@ + Date: Fri, 7 Feb 2025 16:39:59 -0800 Subject: [PATCH 14/38] rebase to latest with common upload v2 --- includes/API.php | 16 ++++++++++++++++ .../API/CommonFeedUploads/Create/Request.php | 2 +- .../API/CommonFeedUploads/Create/Response.php | 2 +- includes/API/CommonFeedUploads/Read/Request.php | 2 +- includes/API/CommonFeedUploads/Read/Response.php | 2 +- 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/includes/API.php b/includes/API.php index 29160aa85..dd418f8d4 100644 --- a/includes/API.php +++ b/includes/API.php @@ -13,6 +13,7 @@ defined( 'ABSPATH' ) or exit; +use WooCommerce\Facebook\API\Exceptions\Request_Limit_Reached; use WooCommerce\Facebook\API\Request; use WooCommerce\Facebook\API\Response; use WooCommerce\Facebook\Events\Event; @@ -552,6 +553,21 @@ public function create_upload( string $product_feed_id, array $data ) { return $this->perform_request( $request ); } + /** + * @param string $cpi_id The commerce partner integration id. + * @param array $data The json body for the Generic Feed Upload endpoint. + * + * @return Response + * @throws Request_Limit_Reached + * @throws ApiException + */ + public function create_common_upload( string $cpi_id, array $data ): Response { + $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); + $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); + + return $this->perform_request( $request ); + } + /** * @param string $external_merchant_settings_id diff --git a/includes/API/CommonFeedUploads/Create/Request.php b/includes/API/CommonFeedUploads/Create/Request.php index f14597514..e773eafb3 100644 --- a/includes/API/CommonFeedUploads/Create/Request.php +++ b/includes/API/CommonFeedUploads/Create/Request.php @@ -1,7 +1,7 @@ Date: Tue, 18 Feb 2025 15:59:52 -0800 Subject: [PATCH 15/38] combine feedtype and feedfactory into feed manager, create example feed --- includes/Feed/AbstractFeed.php | 56 ++++------------------------ includes/Feed/ExampleFeed.php | 54 +++++++++++++++++++++++++++ includes/Feed/FeedManager.php | 68 +++++++++++++++++++++++++++++----- 3 files changed, 120 insertions(+), 58 deletions(-) create mode 100644 includes/Feed/ExampleFeed.php diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 36967c8da..a0f92d29e 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -32,26 +32,6 @@ abstract class AbstractFeed { /** The action slug for triggering file upload */ const FEED_GEN_COMPLETE_ACTION = 'wc_facebook_feed_generation_completed_'; - /** The WordPress option name where the secret included in the feed URL is stored */ - const OPTION_FEED_URL_SECRET = 'wc_facebook_feed_url_secret_'; - - /** The feed name for creating a new feed by this plugin. Modify in extending class. */ - const FEED_NAME = ' by Facebook for WooCommerce plugin. DO NOT DELETE.'; - - /** - * The feed ID for the given feed. - * - * @var string - */ - protected string $feed_id; - - /** - * The upload ID for the given feed. - * - * @var string - */ - protected string $upload_id; - /** * The name of the data stream to be synced via this feed. * @@ -73,30 +53,26 @@ abstract class AbstractFeed { */ protected FeedHandler $feed_handler; - /** - * The feed info to tracker instance for the given feed. - * - * @var FeedInfoToTracker - */ - protected FeedInfoToTracker $feed_info_to_tracker; - /** * Constructor. * * Initializes the feed with the given data stream name and adds the necessary hooks. * * @param string $data_stream_name The name of the data stream. + * @param string $heartbeat The heartbeat interval for the feed generation. */ - public function __construct( string $data_stream_name ) { + public function __construct( string $data_stream_name, string $heartbeat ) { self::$data_stream_name = $data_stream_name; - $this->add_hooks(); + $this->add_hooks( $heartbeat ); } /** * Adds the necessary hooks for feed generation and data request handling. + * + * @param string $heartbeat The heartbeat interval for the feed generation. */ - private function add_hooks() { - add_action( Heartbeat::HOURLY, $this->schedule_feed_generation() ); + private function add_hooks( string $heartbeat ) { + add_action( $heartbeat, $this->schedule_feed_generation() ); add_action( self::modify_action_name( self::GENERATE_FEED_ACTION ), $this->regenerate_feed() ); add_action( self::modify_action_name( self::FEED_GEN_COMPLETE_ACTION ), $this->send_request_to_upload_feed() ); add_action( 'woocommerce_api_' . self::modify_action_name( self::REQUEST_FEED_ACTION ), $this->handle_feed_data_request() ); @@ -131,24 +107,6 @@ abstract public function send_request_to_upload_feed(); */ abstract public function handle_feed_data_request(); - /** - * Feed id to be used in upload - * - * @return string - */ - public function get_feed_id(): string { - return $this->feed_id; - } - - /** - * Upload id to be used in upload - * - * @return string - */ - public function get_upload_id(): string { - return $this->upload_id; - } - /** * Gets the URL for retrieving the product feed data. * diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php new file mode 100644 index 000000000..d4eb8f803 --- /dev/null +++ b/includes/Feed/ExampleFeed.php @@ -0,0 +1,54 @@ +feed_factory = new FeedFactory(); - $this->feed_types = FeedType::get_feed_types(); + $this->feed_types = $this->get_feed_types(); foreach ( $this->feed_types as $feed_type ) { - $this->feed_instances[ $feed_type ] = $this->feed_factory->create_feed( $feed_type ); + $this->feed_instances[ $feed_type ] = $this->create_feed( $feed_type ); } } + + /** + * Create a feed based on the data stream name. + * + * @param string $data_stream_name The name of the data stream. + * + * @return AbstractFeed The created feed instance derived from AbstractFeed. + * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType. + */ + private function create_feed( string $data_stream_name ): AbstractFeed { + switch ( $data_stream_name ) { + case self::EXAMPLE: + return new ExampleFeed( $data_stream_name, Heartbeat::HOURLY ); + default: + throw new \InvalidArgumentException( 'Invalid data stream name' ); + } + } + + /** + * Get the list of feed types. + * + * @return array + */ + public static function get_feed_types(): array { + return array( self::EXAMPLE ); + } + + /** + * Get the feed file writer for the given data stream name. + * + * @param string $data_stream_name The name of the data stream. + * + * @return FeedFileWriter + * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType. + */ + public static function get_feed_file_writer( string $data_stream_name ): FeedFileWriter { + switch ( $data_stream_name ) { + case self::EXAMPLE: + return new CsvFeedFileWriter( $data_stream_name ); + default: + throw new \InvalidArgumentException( 'Invalid data stream name' ); + } + } + + /** + * Get the feed instance for the given feed type. + * + * @param string $feed_type the specific feed in question. + * + * @return AbstractFeed + */ + public function get_feed_instance( string $feed_type ): AbstractFeed { + return $this->feed_instances[ $feed_type ]; + } } From f9dc4d092233c949d745d652e4fa8366ce0ae332 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Tue, 18 Feb 2025 17:09:34 -0800 Subject: [PATCH 16/38] expand on example feed --- includes/Feed/CsvFeedFileWriter.php | 4 +- includes/Feed/ExampleFeed.php | 69 ++++++++++++++++++++++++---- includes/Feed/ExampleFeedHandler.php | 48 +++++++++++++++++++ includes/Feed/FeedFileWriter.php | 2 +- includes/Feed/FeedHandler.php | 16 ------- 5 files changed, 111 insertions(+), 28 deletions(-) create mode 100644 includes/Feed/ExampleFeedHandler.php diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index c67b2f767..507c8737d 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -45,11 +45,11 @@ public function write_feed_file() { } /** - * Creates files in the catalog feed directory to prevent directory listing and hotlinking. + * Creates files in the given feed directory to prevent directory listing and hotlinking. * * @since 1.11.0 */ - public function create_files_to_protect_product_feed_directory() { + public function create_files_to_protect_feed_directory() { } /** diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index d4eb8f803..534206371 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -1,43 +1,90 @@ feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); + $scheduler = new ActionScheduler(); + $this->feed_generator = new FeedGenerator( $scheduler, $this->feed_handler ); + $this->feed_generator->init(); + + parent::__construct( $data_stream_name, Heartbeat::HOURLY ); + } + + /** + * Schedules the recurring feed generation. + * + * This method must be implemented by the concrete feed class, usually by providing a recurring interval */ public function schedule_feed_generation() { // TODO: Implement schedule_feed_generation() method. } /** - * @inheritDoc + * Regenerates the example feed. + * + * This method is responsible for initiating the regeneration of the example feed. + * The method ensures that the feed is regenerated based on the defined schedule. */ public function regenerate_feed() { - // TODO: Implement regenerate_feed() method. + // Maybe use new ( experimental ), feed generation framework. + if ( facebook_for_woocommerce()->get_integration()->is_new_style_feed_generation_enabled() ) { + $this->feed_generator->queue_start(); + } else { + $this->feed_handler->generate_feed_file(); + } } /** - * @inheritDoc + * Trigger the upload flow + * + * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload */ public function send_request_to_upload_feed() { // TODO: Implement send_request_to_upload_feed() method. } /** - * @inheritDoc + * Handles the feed data request. + * + * This method must be implemented by the concrete feed class. */ public function handle_feed_data_request() { // TODO: Implement handle_feed_data_request() method. } /** - * @inheritDoc + * Gets the URL for retrieving the feed data. + * + * This method must be implemented by the concrete feed class. + * + * @return string The URL for retrieving the feed data. */ public static function get_feed_data_url(): string { // TODO: Implement get_feed_data_url() method. @@ -45,7 +92,11 @@ public static function get_feed_data_url(): string { } /** - * @inheritDoc + * Gets the secret value that should be included in the ExampleFeed URL. + * + * This method must be implemented by the concrete feed class. + * + * @return string The secret value for the ExampleFeed URL. */ public static function get_feed_secret(): string { // TODO: Implement get_feed_secret() method. diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php new file mode 100644 index 000000000..11504afe1 --- /dev/null +++ b/includes/Feed/ExampleFeedHandler.php @@ -0,0 +1,48 @@ +feed_writer = $feed_writer; + } + + /** + * Generate the feed file. + * + * This method is responsible for generating a feed file. + */ + public function generate_feed_file() { + // TODO: Implement generate_feed_file() method. + } +} diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php index d44ad5c2a..74f528643 100644 --- a/includes/Feed/FeedFileWriter.php +++ b/includes/Feed/FeedFileWriter.php @@ -23,7 +23,7 @@ public function write_feed_file(); * * @since 1.11.0 */ - public function create_files_to_protect_product_feed_directory(); + public function create_files_to_protect_feed_directory(); /** * Gets the feed file path of given feed. diff --git a/includes/Feed/FeedHandler.php b/includes/Feed/FeedHandler.php index c3d61b172..58e8d8b1b 100644 --- a/includes/Feed/FeedHandler.php +++ b/includes/Feed/FeedHandler.php @@ -19,20 +19,4 @@ interface FeedHandler { * This method is responsible for generating a feed file. */ public function generate_feed_file(); - - /** - * Get the feed upload status. - * - * @param array $settings the settings of the facebook integration. - * - * @return string the status of the feed upload. - */ - public function get_feed_upload_status( array $settings ): string; - - /** - * Get the item ids to sync for the feed writer. - * - * @return array - */ - public function get_item_ids_to_sync(): array; } From 94da181ebdf829dfbea311f54703a0c21e43a485 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Tue, 18 Feb 2025 17:47:50 -0800 Subject: [PATCH 17/38] example feed generator --- includes/Feed/ExampleFeed.php | 2 +- includes/Feed/ExampleFeedGenerator.php | 67 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 includes/Feed/ExampleFeedGenerator.php diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 534206371..15f53a719 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -31,7 +31,7 @@ public function __construct() { $data_stream_name = FeedManager::EXAMPLE; $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); $scheduler = new ActionScheduler(); - $this->feed_generator = new FeedGenerator( $scheduler, $this->feed_handler ); + $this->feed_generator = new ExampleFeedGenerator( $scheduler, $this->feed_handler ); $this->feed_generator->init(); parent::__construct( $data_stream_name, Heartbeat::HOURLY ); diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php new file mode 100644 index 000000000..d3a15bc2a --- /dev/null +++ b/includes/Feed/ExampleFeedGenerator.php @@ -0,0 +1,67 @@ + Date: Tue, 18 Feb 2025 18:23:46 -0800 Subject: [PATCH 18/38] removed promotions and condensed classes --- includes/Feed/FeedFactory.php | 32 ------ includes/Feed/FeedGeneratorFactory.php | 55 --------- includes/Feed/FeedInfoToTracker.php | 46 -------- includes/Feed/FeedType.php | 46 -------- includes/Promotions/PromotionsFeed.php | 107 ------------------ .../Promotions/PromotionsFeedGenerator.php | 70 ------------ includes/Promotions/PromotionsFeedHandler.php | 75 ------------ .../PromotionsFeedInfoToTracker.php | 18 --- 8 files changed, 449 deletions(-) delete mode 100644 includes/Feed/FeedFactory.php delete mode 100644 includes/Feed/FeedGeneratorFactory.php delete mode 100644 includes/Feed/FeedInfoToTracker.php delete mode 100644 includes/Feed/FeedType.php delete mode 100644 includes/Promotions/PromotionsFeed.php delete mode 100644 includes/Promotions/PromotionsFeedGenerator.php delete mode 100644 includes/Promotions/PromotionsFeedHandler.php delete mode 100644 includes/Promotions/PromotionsFeedInfoToTracker.php diff --git a/includes/Feed/FeedFactory.php b/includes/Feed/FeedFactory.php deleted file mode 100644 index a186379fb..000000000 --- a/includes/Feed/FeedFactory.php +++ /dev/null @@ -1,32 +0,0 @@ -init(); - $this->feed_generators[ $data_stream_name ] = $promotions_feed_generator; - } - } - - /** - * Get the feed generator instance. - * - * @param string $feed_gen_class_name The class name of the feed generator. - * - * @return FeedGenerator - */ - public function get_feed_generator( string $feed_gen_class_name ): FeedGenerator { - return $this->feed_generators[ $feed_gen_class_name ]; - } -} diff --git a/includes/Feed/FeedInfoToTracker.php b/includes/Feed/FeedInfoToTracker.php deleted file mode 100644 index 93c161f75..000000000 --- a/includes/Feed/FeedInfoToTracker.php +++ /dev/null @@ -1,46 +0,0 @@ -get_data_source_feed_tracker_info(); - facebook_for_woocommerce()->get_tracker()->track_facebook_feed_config( $info ); - } catch ( Exception $error ) { - facebook_for_woocommerce()->log( 'Unable to detect valid feed configuration: ' . $error->getMessage() ); - } - } - - /** - * Get data needed to set transient in the global Tracker class. - */ - abstract protected function get_data_source_feed_tracker_info(); -} diff --git a/includes/Feed/FeedType.php b/includes/Feed/FeedType.php deleted file mode 100644 index 1af002586..000000000 --- a/includes/Feed/FeedType.php +++ /dev/null @@ -1,46 +0,0 @@ -job_manager->generator_factory; - $this->feed_generator = $generator_factory->get_feed_generator( $data_stream_name ); - $this->feed_handler = new PromotionsFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); - // The daily heartbeat is guess for now, can change as needed. - $this->feed_info_to_tracker = new PromotionsFeedInfoToTracker( Heartbeat::DAILY ); - parent::__construct( $data_stream_name ); - } - - /** - * Schedules the recurring feed generation. - * - * This method must be implemented by the concrete feed class, usually by providing a recurring interval - */ - public function schedule_feed_generation() { - // TODO: Implement schedule_feed_generation() method. - } - - /** - * Regenerates the product feed. - * - * This method is responsible for initiating the regeneration of the product feed. - * The method ensures that the feed is regenerated based on the defined schedule. - */ - public function regenerate_feed() { - // Maybe use new ( experimental ), feed generation framework. - if ( facebook_for_woocommerce()->get_integration()->is_new_style_feed_generation_enabled() ) { - $this->feed_generator->queue_start(); - } else { - $this->feed_handler->generate_feed_file(); - } - } - - /** - * Trigger the upload flow - * - * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload - */ - public function send_request_to_upload_feed() { - // TODO: Implement send_request_to_upload_feed() method. - } - - /** - * Handles the feed data request. - * - * This method must be implemented by the concrete feed class. - */ - public function handle_feed_data_request() { - // TODO: Implement handle_feed_data_request() method. - } - - /** - * Gets the URL for retrieving the product feed data. - * - * This method must be implemented by the concrete feed class. - * - * @return string The URL for retrieving the product feed data. - */ - public static function get_feed_data_url(): string { - // TODO: Implement get_feed_data_url() method. - return ''; - } - - /** - * Gets the secret value that should be included in the ProductFeed URL. - * - * This method must be implemented by the concrete feed class. - * - * @return string The secret value for the ProductFeed URL. - */ - public static function get_feed_secret(): string { - // TODO: Implement get_feed_secret() method. - return ''; - } -} diff --git a/includes/Promotions/PromotionsFeedGenerator.php b/includes/Promotions/PromotionsFeedGenerator.php deleted file mode 100644 index 8ff4a23f8..000000000 --- a/includes/Promotions/PromotionsFeedGenerator.php +++ /dev/null @@ -1,70 +0,0 @@ -feed_writer = $feed_writer; - } - - /** - * Generate the feed file. - * - * This method is responsible for generating a feed file. - */ - public function generate_feed_file() { - // TODO: Implement generate_feed_file() method. - } - - /** - * Get the feed upload status. - * - * @param array $settings the settings of the facebook integration. - * - * @return string the status of the feed upload. - */ - public function get_feed_upload_status( array $settings ): string { - // TODO: Implement get_feed_upload_status() method. - return ''; - } - - /** - * Get the item ids to sync for the feed writer. - * - * @return array - */ - public function get_item_ids_to_sync(): array { - // TODO: Implement get_item_ids_to_sync() method. - return array(); - } -} diff --git a/includes/Promotions/PromotionsFeedInfoToTracker.php b/includes/Promotions/PromotionsFeedInfoToTracker.php deleted file mode 100644 index c4f4046d2..000000000 --- a/includes/Promotions/PromotionsFeedInfoToTracker.php +++ /dev/null @@ -1,18 +0,0 @@ - Date: Tue, 18 Feb 2025 18:41:30 -0800 Subject: [PATCH 19/38] modify request and responses --- .../API/CommonFeedUploads/Create/Request.php | 5 ++-- .../API/CommonFeedUploads/Create/Response.php | 5 +--- .../API/CommonFeedUploads/Read/Request.php | 24 ------------------- .../API/CommonFeedUploads/Read/Response.php | 16 ------------- 4 files changed, 3 insertions(+), 47 deletions(-) delete mode 100644 includes/API/CommonFeedUploads/Read/Request.php delete mode 100644 includes/API/CommonFeedUploads/Read/Response.php diff --git a/includes/API/CommonFeedUploads/Create/Request.php b/includes/API/CommonFeedUploads/Create/Request.php index e773eafb3..d3203af90 100644 --- a/includes/API/CommonFeedUploads/Create/Request.php +++ b/includes/API/CommonFeedUploads/Create/Request.php @@ -9,15 +9,14 @@ /** * Request object for the Common Feed Upload. - * */ class Request extends ApiRequest { /** * Constructs the request. * - * @param string $cpi_id Customer Partner Integration ID. - * @param array $data Facebook Product Feed Data. + * @param string $cpi_id Commerce Partner Integration ID. + * @param array $data Feed Metadata for File Update Post endpoint. */ public function __construct( string $cpi_id, array $data ) { parent::__construct( $cpi_id . '/file_update', 'POST' ); diff --git a/includes/API/CommonFeedUploads/Create/Response.php b/includes/API/CommonFeedUploads/Create/Response.php index 9ade5e5f2..9845c6d49 100644 --- a/includes/API/CommonFeedUploads/Create/Response.php +++ b/includes/API/CommonFeedUploads/Create/Response.php @@ -8,9 +8,6 @@ defined( 'ABSPATH' ) || exit; /** - * Response object for Product Catalog > Product Feed Upload > Create Graph Api. - * - * @link https://developers.facebook.com/docs/marketing-api/reference/product-feed/uploads/#Creating - * @property-read array $data Facebook Product Feeds Upload. + * Response object for Common Feed Upload */ class Response extends ApiResponse {} diff --git a/includes/API/CommonFeedUploads/Read/Request.php b/includes/API/CommonFeedUploads/Read/Request.php deleted file mode 100644 index ddfd3905e..000000000 --- a/includes/API/CommonFeedUploads/Read/Request.php +++ /dev/null @@ -1,24 +0,0 @@ - Date: Tue, 18 Feb 2025 18:48:50 -0800 Subject: [PATCH 20/38] feed immediately and job manager --- includes/Feed/AbstractFeed.php | 6 ++++++ includes/Feed/ExampleFeed.php | 7 +++++++ includes/Jobs/JobManager.php | 9 --------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index a0f92d29e..90753af16 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -107,6 +107,12 @@ abstract public function send_request_to_upload_feed(); */ abstract public function handle_feed_data_request(); + /** Schedules the feed generation immediately, ignoring the interval. + * + * This method must be implemented by the concrete feed class. + */ + abstract public function schedule_feed_generation_immediately(); + /** * Gets the URL for retrieving the product feed data. * diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 15f53a719..499df54a3 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -79,6 +79,13 @@ public function handle_feed_data_request() { // TODO: Implement handle_feed_data_request() method. } + /** + * Allows an admin to schedule the feed generation immediately. + */ + public function schedule_feed_generation_immediately() { + // TODO: Implement schedule_feed_generation_immediately() method. + } + /** * Gets the URL for retrieving the feed data. * diff --git a/includes/Jobs/JobManager.php b/includes/Jobs/JobManager.php index 4cfbaa30f..78ad4de8c 100644 --- a/includes/Jobs/JobManager.php +++ b/includes/Jobs/JobManager.php @@ -34,12 +34,6 @@ class JobManager { */ public $delete_all_products; - /** - * Publicly accessible instance to get the correct generator - * @var FeedGeneratorFactory - */ - public FeedGeneratorFactory $generator_factory; - /** * Instantiate and init all jobs for the plugin. */ @@ -49,9 +43,6 @@ public function init() { $this->generate_product_feed_job = new GenerateProductFeed( $action_scheduler_proxy ); $this->generate_product_feed_job->init(); - // Creates all the feed generators except for product - $this->generator_factory = new FeedGeneratorFactory( $action_scheduler_proxy ); - $this->cleanup_skyverge_job_options = new CleanupSkyvergeFrameworkJobOptions(); $this->cleanup_skyverge_job_options->init(); From 2a77fe8c40d285fe1459c1a8a411b05890dda8dc Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Tue, 18 Feb 2025 18:58:45 -0800 Subject: [PATCH 21/38] since todos --- includes/API/CommonFeedUploads/Create/Request.php | 1 + includes/API/CommonFeedUploads/Create/Response.php | 1 + includes/Feed/AbstractFeed.php | 2 +- includes/Feed/CsvFeedFileWriter.php | 13 ++++++------- includes/Feed/ExampleFeed.php | 2 +- includes/Feed/ExampleFeedGenerator.php | 2 +- includes/Feed/ExampleFeedHandler.php | 2 +- includes/Feed/FeedFileWriter.php | 12 ++++++------ includes/Feed/FeedGenerator.php | 7 +++---- includes/Feed/FeedHandler.php | 1 + includes/Feed/FeedManager.php | 1 + includes/Jobs/JobManager.php | 1 - 12 files changed, 23 insertions(+), 22 deletions(-) diff --git a/includes/API/CommonFeedUploads/Create/Request.php b/includes/API/CommonFeedUploads/Create/Request.php index d3203af90..ea496b1f3 100644 --- a/includes/API/CommonFeedUploads/Create/Request.php +++ b/includes/API/CommonFeedUploads/Create/Request.php @@ -17,6 +17,7 @@ class Request extends ApiRequest { * * @param string $cpi_id Commerce Partner Integration ID. * @param array $data Feed Metadata for File Update Post endpoint. + * Todo: add since */ public function __construct( string $cpi_id, array $data ) { parent::__construct( $cpi_id . '/file_update', 'POST' ); diff --git a/includes/API/CommonFeedUploads/Create/Response.php b/includes/API/CommonFeedUploads/Create/Response.php index 9845c6d49..1666cc1ef 100644 --- a/includes/API/CommonFeedUploads/Create/Response.php +++ b/includes/API/CommonFeedUploads/Create/Response.php @@ -9,5 +9,6 @@ /** * Response object for Common Feed Upload + * Todo: add since */ class Response extends ApiResponse {} diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 90753af16..b9241c916 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -21,7 +21,7 @@ * This class defines the structure and common methods that must be implemented by any concrete feed class. * * @package WooCommerce\Facebook\ProductFeed - * @since 1.11.0 + * * Todo: add since. */ abstract class AbstractFeed { /** The action callback for generating a feed */ diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index 507c8737d..202856cec 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -46,8 +46,7 @@ public function write_feed_file() { /** * Creates files in the given feed directory to prevent directory listing and hotlinking. - * - * @since 1.11.0 + * Todo: add since */ public function create_files_to_protect_feed_directory() { } @@ -56,7 +55,7 @@ public function create_files_to_protect_feed_directory() { * Gets the feed file path of given feed. * * @return string - * @since 1.11.0 + * * Todo: add since */ public function get_file_path(): string { return ''; @@ -67,7 +66,7 @@ public function get_file_path(): string { * Gets the temporary feed file path. * * @return string - * @since 1.11.3 + * * Todo: add since */ public function get_temp_file_path(): string { return ''; @@ -77,7 +76,7 @@ public function get_temp_file_path(): string { * Gets the feed file directory. * * @return string - * @since 1.11.0 + * * Todo: add since */ public function get_file_directory(): string { return ''; @@ -88,7 +87,7 @@ public function get_file_directory(): string { * Gets the feed file name. * * @return string - * @since 1.11.0 + * * Todo: add since */ public function get_file_name(): string { return ''; @@ -100,7 +99,7 @@ public function get_file_name(): string { * @param string $secret The secret used to generate the file name. * * @return string - * @since 1.11.3 + * * Todo: add since */ public function get_temp_file_name( string $secret ): string { return $secret; diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 499df54a3..4d255200e 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -20,7 +20,7 @@ * Extends Abstract Feed class to handle example feed requests and generation for Facebook integration. * * @package WooCommerce\Facebook\Feed - * @since 1.11.0 + * * Todo: add since */ class ExampleFeed extends AbstractFeed { diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php index d3a15bc2a..4003424da 100644 --- a/includes/Feed/ExampleFeedGenerator.php +++ b/includes/Feed/ExampleFeedGenerator.php @@ -10,7 +10,7 @@ * This class generates the feed as a batch job. * * @package WooCommerce\Facebook\Feed - * @since 2.5.0 + * * Todo: add since */ class ExampleFeedGenerator extends FeedGenerator { /** diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php index 11504afe1..385b39013 100644 --- a/includes/Feed/ExampleFeedHandler.php +++ b/includes/Feed/ExampleFeedHandler.php @@ -18,7 +18,7 @@ * Extends the FeedHandler interface to handle example feed file generation. * * @package WooCommerce\Facebook\ProductFeed - * @since 1.11.0 + * * Todo: add since */ class ExampleFeedHandler implements FeedHandler { /** diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php index 74f528643..57553992a 100644 --- a/includes/Feed/FeedFileWriter.php +++ b/includes/Feed/FeedFileWriter.php @@ -21,7 +21,7 @@ public function write_feed_file(); /** * Creates files in the catalog feed directory to prevent directory listing and hotlinking. * - * @since 1.11.0 + * * Todo: add since */ public function create_files_to_protect_feed_directory(); @@ -29,7 +29,7 @@ public function create_files_to_protect_feed_directory(); * Gets the feed file path of given feed. * * @return string - * @since 1.11.0 + * * Todo: add since */ public function get_file_path(): string; @@ -38,7 +38,7 @@ public function get_file_path(): string; * Gets the temporary feed file path. * * @return string - * @since 1.11.3 + * * Todo: add since */ public function get_temp_file_path(): string; @@ -46,7 +46,7 @@ public function get_temp_file_path(): string; * Gets the feed file directory. * * @return string - * @since 1.11.0 + * * Todo: add since */ public function get_file_directory(): string; @@ -55,7 +55,7 @@ public function get_file_directory(): string; * Gets the feed file name. * * @return string - * @since 1.11.0 + * * Todo: add since */ public function get_file_name(): string; @@ -65,7 +65,7 @@ public function get_file_name(): string; * @param string $secret The secret to use for the temporary file name. * * @return string - * @since 1.11.3 + * * Todo: add since */ public function get_temp_file_name( string $secret ): string; } diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index c77c047d3..2235c2bb0 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -16,7 +16,7 @@ * It extends the AbstractChainedJob class to utilize the Action Scheduler framework for batch processing. * * @package WooCommerce\Facebook\Feed - * @since 2.5.0 + * * Todo: add since */ class FeedGenerator extends AbstractChainedJob { /** @@ -30,6 +30,7 @@ class FeedGenerator extends AbstractChainedJob { * FeedGenerator constructor. * * @param ActionSchedulerInterface $action_scheduler The action scheduler instance. + * @param FeedHandler $feed_handler The feed handler instance. */ public function __construct( ActionSchedulerInterface $action_scheduler, FeedHandler $feed_handler ) { parent::__construct( $action_scheduler ); @@ -71,9 +72,7 @@ protected function get_items_for_batch( int $batch_number, array $args ): array * * @param array $items The items of the current batch, probably compiled as an object. * @param array $args The args for the job. - * - * @throws Exception On error. The failure will be logged by Action Scheduler and the job chain will stop. - * @since 1.1.0 + * Todo: add since. */ protected function process_items( array $items, array $args ) { } diff --git a/includes/Feed/FeedHandler.php b/includes/Feed/FeedHandler.php index 58e8d8b1b..6284ea9b2 100644 --- a/includes/Feed/FeedHandler.php +++ b/includes/Feed/FeedHandler.php @@ -17,6 +17,7 @@ interface FeedHandler { * Generate the feed file. * * This method is responsible for generating a feed file. + * Todo: add since */ public function generate_feed_file(); } diff --git a/includes/Feed/FeedManager.php b/includes/Feed/FeedManager.php index 2f5ef62e7..ec1468dad 100644 --- a/includes/Feed/FeedManager.php +++ b/includes/Feed/FeedManager.php @@ -7,6 +7,7 @@ /** * Responsible for creating and managing feeds. * Global manipulations of the feed such as updating feed and upload ID to be made through this class. + * Todo: add since */ class FeedManager { const EXAMPLE = 'example'; diff --git a/includes/Jobs/JobManager.php b/includes/Jobs/JobManager.php index 78ad4de8c..a15bba36e 100644 --- a/includes/Jobs/JobManager.php +++ b/includes/Jobs/JobManager.php @@ -3,7 +3,6 @@ namespace WooCommerce\Facebook\Jobs; use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionScheduler; -use WooCommerce\Facebook\Feed\FeedGeneratorFactory; defined( 'ABSPATH' ) || exit; From a5f6414a41efafe1dba5e23c4b8a6e2c9fbe6481 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Wed, 19 Feb 2025 11:49:38 -0800 Subject: [PATCH 22/38] add since tags and fix phpcs issues --- includes/Feed/AbstractFeed.php | 20 ++++++++++++- includes/Feed/CsvFeedFileWriter.php | 19 ++++++++----- includes/Feed/ExampleFeed.php | 20 +++++++++---- includes/Feed/ExampleFeedGenerator.php | 39 ++++++++++++++++++++++++-- includes/Feed/ExampleFeedHandler.php | 5 +++- includes/Feed/FeedFileWriter.php | 14 +++++---- includes/Feed/FeedGenerator.php | 23 +++++++++++---- includes/Feed/FeedHandler.php | 5 ++-- includes/Feed/FeedManager.php | 13 +++++++-- 9 files changed, 124 insertions(+), 34 deletions(-) diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index b9241c916..730bbbb2c 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -21,7 +21,7 @@ * This class defines the structure and common methods that must be implemented by any concrete feed class. * * @package WooCommerce\Facebook\ProductFeed - * * Todo: add since. + * @since 3.5.0 */ abstract class AbstractFeed { /** The action callback for generating a feed */ @@ -36,6 +36,7 @@ abstract class AbstractFeed { * The name of the data stream to be synced via this feed. * * @var string + * @since 3.5.0 */ private static string $data_stream_name; @@ -43,6 +44,7 @@ abstract class AbstractFeed { * The feed generator instance for the given feed. * * @var FeedGenerator + * @since 3.5.0 */ protected FeedGenerator $feed_generator; @@ -50,6 +52,7 @@ abstract class AbstractFeed { * The feed handler instance for the given feed. * * @var FeedHandler + * @since 3.5.0 */ protected FeedHandler $feed_handler; @@ -60,6 +63,7 @@ abstract class AbstractFeed { * * @param string $data_stream_name The name of the data stream. * @param string $heartbeat The heartbeat interval for the feed generation. + * @since 3.5.0 */ public function __construct( string $data_stream_name, string $heartbeat ) { self::$data_stream_name = $data_stream_name; @@ -70,6 +74,7 @@ public function __construct( string $data_stream_name, string $heartbeat ) { * Adds the necessary hooks for feed generation and data request handling. * * @param string $heartbeat The heartbeat interval for the feed generation. + * @since 3.5.0 */ private function add_hooks( string $heartbeat ) { add_action( $heartbeat, $this->schedule_feed_generation() ); @@ -82,6 +87,8 @@ private function add_hooks( string $heartbeat ) { * Schedules the recurring feed generation. * * This method must be implemented by the concrete feed class, usually by providing a recurring interval + * + * @since 3.5.0 */ abstract public function schedule_feed_generation(); @@ -90,6 +97,8 @@ abstract public function schedule_feed_generation(); * * This method is responsible for initiating the regeneration of the product feed. * The method ensures that the feed is regenerated based on the defined schedule. + * + * @since 3.5.0 */ abstract public function regenerate_feed(); @@ -97,6 +106,8 @@ abstract public function regenerate_feed(); * Trigger the upload flow * * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload + * + * @since 3.5.0 */ abstract public function send_request_to_upload_feed(); @@ -104,12 +115,16 @@ abstract public function send_request_to_upload_feed(); * Handles the feed data request. * * This method must be implemented by the concrete feed class. + * + * @since 3.5.0 */ abstract public function handle_feed_data_request(); /** Schedules the feed generation immediately, ignoring the interval. * * This method must be implemented by the concrete feed class. + * + * @since 3.5.0 */ abstract public function schedule_feed_generation_immediately(); @@ -119,6 +134,7 @@ abstract public function schedule_feed_generation_immediately(); * This method must be implemented by the concrete feed class. * * @return string The URL for retrieving the product feed data. + * @since 3.5.0 */ abstract public static function get_feed_data_url(): string; @@ -128,6 +144,7 @@ abstract public static function get_feed_data_url(): string; * This method must be implemented by the concrete feed class. * * @return string The secret value for the ProductFeed URL. + * @since 3.5.0 */ abstract public static function get_feed_secret(): string; @@ -137,6 +154,7 @@ abstract public static function get_feed_secret(): string; * @param string $feed_name The base feed name. * * @return string The modified action name. + * @since 3.5.0 */ protected static function modify_action_name( string $feed_name ): string { return $feed_name . self::$data_stream_name; diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index 202856cec..d516acfaa 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -15,14 +15,16 @@ /** * * CsvFeedFileWriter class - * To be used by the feed handler to write its updates to its feed file. * To be used by any feed handler whose feed requires a csv file. + * + * @since 3.5.0 */ class CsvFeedFileWriter implements FeedFileWriter { /** * Use the feed name to distinguish which folder to write to. * * @var string + * @since 3.5.0 */ private $feed_name; @@ -30,6 +32,7 @@ class CsvFeedFileWriter implements FeedFileWriter { * Constructor. * * @param string $feed_name The name of the feed. + * @since 3.5.0 */ public function __construct( string $feed_name ) { $this->feed_name = $feed_name; @@ -39,6 +42,7 @@ public function __construct( string $feed_name ) { * Write the feed file. * * @return void + * @since 3.5.0 */ public function write_feed_file() { // TODO: Implement write_feed_file() method. @@ -46,7 +50,8 @@ public function write_feed_file() { /** * Creates files in the given feed directory to prevent directory listing and hotlinking. - * Todo: add since + * + * @since 3.5.0 */ public function create_files_to_protect_feed_directory() { } @@ -55,7 +60,7 @@ public function create_files_to_protect_feed_directory() { * Gets the feed file path of given feed. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_file_path(): string { return ''; @@ -66,7 +71,7 @@ public function get_file_path(): string { * Gets the temporary feed file path. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_temp_file_path(): string { return ''; @@ -76,7 +81,7 @@ public function get_temp_file_path(): string { * Gets the feed file directory. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_file_directory(): string { return ''; @@ -87,7 +92,7 @@ public function get_file_directory(): string { * Gets the feed file name. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_file_name(): string { return ''; @@ -99,7 +104,7 @@ public function get_file_name(): string { * @param string $secret The secret used to generate the file name. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_temp_file_name( string $secret ): string { return $secret; diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 4d255200e..6b210ca92 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -20,12 +20,14 @@ * Extends Abstract Feed class to handle example feed requests and generation for Facebook integration. * * @package WooCommerce\Facebook\Feed - * * Todo: add since + * @since 3.5.0 */ class ExampleFeed extends AbstractFeed { /** * Constructor. + * + * @since 3.5.0 */ public function __construct() { $data_stream_name = FeedManager::EXAMPLE; @@ -41,16 +43,17 @@ public function __construct() { * Schedules the recurring feed generation. * * This method must be implemented by the concrete feed class, usually by providing a recurring interval + * + * @since 3.5.0 */ public function schedule_feed_generation() { // TODO: Implement schedule_feed_generation() method. } /** - * Regenerates the example feed. + * Regenerates the example feed based on the defined schedule. * - * This method is responsible for initiating the regeneration of the example feed. - * The method ensures that the feed is regenerated based on the defined schedule. + * @since 3.5.0 */ public function regenerate_feed() { // Maybe use new ( experimental ), feed generation framework. @@ -63,8 +66,9 @@ public function regenerate_feed() { /** * Trigger the upload flow - * * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload + * + * @since 3.5.0 */ public function send_request_to_upload_feed() { // TODO: Implement send_request_to_upload_feed() method. @@ -74,6 +78,8 @@ public function send_request_to_upload_feed() { * Handles the feed data request. * * This method must be implemented by the concrete feed class. + * + * @since 3.5.0 */ public function handle_feed_data_request() { // TODO: Implement handle_feed_data_request() method. @@ -81,6 +87,8 @@ public function handle_feed_data_request() { /** * Allows an admin to schedule the feed generation immediately. + * + * @since 3.5.0 */ public function schedule_feed_generation_immediately() { // TODO: Implement schedule_feed_generation_immediately() method. @@ -92,6 +100,7 @@ public function schedule_feed_generation_immediately() { * This method must be implemented by the concrete feed class. * * @return string The URL for retrieving the feed data. + * @since 3.5.0 */ public static function get_feed_data_url(): string { // TODO: Implement get_feed_data_url() method. @@ -104,6 +113,7 @@ public static function get_feed_data_url(): string { * This method must be implemented by the concrete feed class. * * @return string The secret value for the ExampleFeed URL. + * @since 3.5.0 */ public static function get_feed_secret(): string { // TODO: Implement get_feed_secret() method. diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php index 4003424da..e58b87124 100644 --- a/includes/Feed/ExampleFeedGenerator.php +++ b/includes/Feed/ExampleFeedGenerator.php @@ -2,6 +2,8 @@ namespace WooCommerce\Facebook\Feed; +use WC_Facebookcommerce; + defined( 'ABSPATH' ) || exit; /** @@ -10,58 +12,91 @@ * This class generates the feed as a batch job. * * @package WooCommerce\Facebook\Feed - * * Todo: add since + * @since 3.5.0 */ class ExampleFeedGenerator extends FeedGenerator { /** + * Handles the start of the feed generation process. + * * @inheritdoc + * @since 3.5.0 */ protected function handle_start() { } /** + * Handles the end of the feed generation process. + * * @inheritdoc + * @since 3.5.0 */ protected function handle_end() { } /** + * Retrieves items for a specific batch. + * + * @param int $batch_number The batch number. + * @param array $args Additional arguments. + * @return array The items for the batch. * @inheritdoc + * @since 3.5.0 */ protected function get_items_for_batch( int $batch_number, array $args ): array { return array(); } /** + * Processes a batch of items. + * + * @param array $items The items to process. + * @param array $args Additional arguments. * @inheritdoc + * @since 3.5.0 */ protected function process_items( array $items, array $args ) { } /** + * Processes a single item. + * + * @param mixed $item The item to process. + * @param array $args Additional arguments. * @inheritdoc + * @since 3.5.0 */ protected function process_item( $item, array $args ) { } /** + * Gets the name of the feed generator. + * + * @return string The name of the feed generator. * @inheritdoc + * @since 3.5.0 */ public function get_name(): string { return ''; } /** + * Gets the plugin name associated with the feed generator. + * + * @return string The plugin name. * @inheritdoc + * @since 3.5.0 */ public function get_plugin_name(): string { return WC_Facebookcommerce::PLUGIN_ID; } /** + * Gets the batch size for the feed generation process. + * + * @return int The batch size. * @inheritdoc */ protected function get_batch_size(): int { - return - 1; + return -1; } } diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php index 385b39013..74e8ed040 100644 --- a/includes/Feed/ExampleFeedHandler.php +++ b/includes/Feed/ExampleFeedHandler.php @@ -18,13 +18,14 @@ * Extends the FeedHandler interface to handle example feed file generation. * * @package WooCommerce\Facebook\ProductFeed - * * Todo: add since + * @since 3.5.0 */ class ExampleFeedHandler implements FeedHandler { /** * The feed writer instance for the given feed. * * @var FeedFileWriter + * @since 3.5.0 */ private FeedFileWriter $feed_writer; @@ -32,6 +33,7 @@ class ExampleFeedHandler implements FeedHandler { * Constructor. * * @param FeedFileWriter $feed_writer An instance of csv feed writer. + * @since 3.5.0 */ public function __construct( FeedFileWriter $feed_writer ) { $this->feed_writer = $feed_writer; @@ -41,6 +43,7 @@ public function __construct( FeedFileWriter $feed_writer ) { * Generate the feed file. * * This method is responsible for generating a feed file. + * @since 3.5.0 */ public function generate_feed_file() { // TODO: Implement generate_feed_file() method. diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php index 57553992a..534de5c07 100644 --- a/includes/Feed/FeedFileWriter.php +++ b/includes/Feed/FeedFileWriter.php @@ -15,13 +15,15 @@ interface FeedFileWriter { /** * Write the feed file. + * + * @since 3.5.0 */ public function write_feed_file(); /** * Creates files in the catalog feed directory to prevent directory listing and hotlinking. * - * * Todo: add since + * @since 3.5.0 */ public function create_files_to_protect_feed_directory(); @@ -29,7 +31,7 @@ public function create_files_to_protect_feed_directory(); * Gets the feed file path of given feed. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_file_path(): string; @@ -38,7 +40,7 @@ public function get_file_path(): string; * Gets the temporary feed file path. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_temp_file_path(): string; @@ -46,7 +48,7 @@ public function get_temp_file_path(): string; * Gets the feed file directory. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_file_directory(): string; @@ -55,7 +57,7 @@ public function get_file_directory(): string; * Gets the feed file name. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_file_name(): string; @@ -65,7 +67,7 @@ public function get_file_name(): string; * @param string $secret The secret to use for the temporary file name. * * @return string - * * Todo: add since + * @since 3.5.0 */ public function get_temp_file_name( string $secret ): string; } diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index 2235c2bb0..6321eef81 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -16,13 +16,14 @@ * It extends the AbstractChainedJob class to utilize the Action Scheduler framework for batch processing. * * @package WooCommerce\Facebook\Feed - * * Todo: add since + * @since 3.5.0 */ class FeedGenerator extends AbstractChainedJob { /** * The feed handler instance for the given feed. * * @var FeedHandler + * @since 3.5.0 */ protected FeedHandler $feed_handler; @@ -30,7 +31,8 @@ class FeedGenerator extends AbstractChainedJob { * FeedGenerator constructor. * * @param ActionSchedulerInterface $action_scheduler The action scheduler instance. - * @param FeedHandler $feed_handler The feed handler instance. + * @param FeedHandler $feed_handler The feed handler instance. + * @since 3.5.0 */ public function __construct( ActionSchedulerInterface $action_scheduler, FeedHandler $feed_handler ) { parent::__construct( $action_scheduler ); @@ -40,6 +42,8 @@ public function __construct( ActionSchedulerInterface $action_scheduler, FeedHan /** * Called before starting the job. * Override for specific data stream. + * + * @since 3.5.0 */ protected function handle_start() { } @@ -47,6 +51,8 @@ protected function handle_start() { /** * Called after the finishing the job. * Override for specific data stream. + * + * @since 3.5.0 */ protected function handle_end() { } @@ -58,9 +64,9 @@ protected function handle_end() { * ASCENDING. This is so that any newly added items will not disrupt the query offset. * Override with your custom SQL logic. * - * @param int $batch_number The batch number increments for each new batch in the job cycle. + * @param int $batch_number The batch number increments for each new batch in the job cycle. * @param array $args The args for the job. - * + * @since 3.5.0 * @throws Exception On error. The failure will be logged by Action Scheduler and the job chain will stop. */ protected function get_items_for_batch( int $batch_number, array $args ): array { @@ -72,7 +78,7 @@ protected function get_items_for_batch( int $batch_number, array $args ): array * * @param array $items The items of the current batch, probably compiled as an object. * @param array $args The args for the job. - * Todo: add since. + * @since 3.5.0 */ protected function process_items( array $items, array $args ) { } @@ -81,7 +87,9 @@ protected function process_items( array $items, array $args ) { * The single item processing logic. Might not need if only using the whole batch. * * @param object $item the singular item to process. This method might not be used but needed to extend parent. - * @param array $args the args for the job. + * @param array $args the args for the job. + * + * @since 3.5.0 */ protected function process_item( $item, array $args ) { } @@ -91,6 +99,7 @@ protected function process_item( $item, array $args ) { * Ex. generate_product_feed * * @return string + * @since 3.5.0 */ public function get_name(): string { return ''; @@ -100,6 +109,7 @@ public function get_name(): string { * Get the name/slug of the plugin that owns the job. * * @return string + * @since 3.5.0 */ public function get_plugin_name(): string { return WC_Facebookcommerce::PLUGIN_ID; @@ -109,6 +119,7 @@ public function get_plugin_name(): string { * Get the job's batch size. * * @return int + * @since 3.5.0 */ protected function get_batch_size(): int { return - 1; diff --git a/includes/Feed/FeedHandler.php b/includes/Feed/FeedHandler.php index 6284ea9b2..8ebdf72ee 100644 --- a/includes/Feed/FeedHandler.php +++ b/includes/Feed/FeedHandler.php @@ -14,10 +14,9 @@ interface FeedHandler { /** - * Generate the feed file. + * Responsible for generating a feed file. * - * This method is responsible for generating a feed file. - * Todo: add since + * @since 3.5.0 */ public function generate_feed_file(); } diff --git a/includes/Feed/FeedManager.php b/includes/Feed/FeedManager.php index ec1468dad..03173cff8 100644 --- a/includes/Feed/FeedManager.php +++ b/includes/Feed/FeedManager.php @@ -7,7 +7,7 @@ /** * Responsible for creating and managing feeds. * Global manipulations of the feed such as updating feed and upload ID to be made through this class. - * Todo: add since + * @since 3.5.0 */ class FeedManager { const EXAMPLE = 'example'; @@ -16,6 +16,7 @@ class FeedManager { * The list of feed types as named strings. * * @var array The list of feed types as named strings. + * @since 3.5.0 */ private array $feed_types; @@ -23,12 +24,15 @@ class FeedManager { * The map of feed types to their instances. * * @var array The map of feed types to their instances. + * @since 3.5.0 */ private array $feed_instances = array(); /** * FeedManager constructor. * Instantiates all the registered feed types and keeps in map. + * + * @since 3.5.0 */ public function __construct() { $this->feed_types = $this->get_feed_types(); @@ -44,6 +48,7 @@ public function __construct() { * * @return AbstractFeed The created feed instance derived from AbstractFeed. * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType. + * @since 3.5.0 */ private function create_feed( string $data_stream_name ): AbstractFeed { switch ( $data_stream_name ) { @@ -58,6 +63,7 @@ private function create_feed( string $data_stream_name ): AbstractFeed { * Get the list of feed types. * * @return array + * @since 3.5.0 */ public static function get_feed_types(): array { return array( self::EXAMPLE ); @@ -69,7 +75,8 @@ public static function get_feed_types(): array { * @param string $data_stream_name The name of the data stream. * * @return FeedFileWriter - * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType. + * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType + * @since 3.5.0 */ public static function get_feed_file_writer( string $data_stream_name ): FeedFileWriter { switch ( $data_stream_name ) { @@ -84,8 +91,8 @@ public static function get_feed_file_writer( string $data_stream_name ): FeedFil * Get the feed instance for the given feed type. * * @param string $feed_type the specific feed in question. - * * @return AbstractFeed + * @since 3.5.0 */ public function get_feed_instance( string $feed_type ): AbstractFeed { return $this->feed_instances[ $feed_type ]; From 954fa0a62b30dd868df1f622e43b8cb3c322c8d0 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Thu, 20 Feb 2025 13:07:41 -0800 Subject: [PATCH 23/38] Changing comments and phpcs problems --- class-wc-facebookcommerce.php | 1 + includes/Feed/AbstractFeed.php | 41 +++++++++------------------- includes/Feed/ExampleFeed.php | 18 ++++++------ includes/Feed/ExampleFeedHandler.php | 3 +- includes/Feed/FeedGenerator.php | 2 +- includes/Feed/FeedManager.php | 3 +- 6 files changed, 28 insertions(+), 40 deletions(-) diff --git a/class-wc-facebookcommerce.php b/class-wc-facebookcommerce.php index 698c8eb1d..bdf518529 100644 --- a/class-wc-facebookcommerce.php +++ b/class-wc-facebookcommerce.php @@ -190,6 +190,7 @@ public function init() { $this->heartbeat = new Heartbeat( WC()->queue() ); $this->heartbeat->init(); + $this->feed_manager = new WooCommerce\Facebook\Feed\FeedManager(); $this->product_feed = new WooCommerce\Facebook\Products\Feed(); $this->products_stock_handler = new WooCommerce\Facebook\Products\Stock(); $this->products_sync_handler = new WooCommerce\Facebook\Products\Sync(); diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 730bbbb2c..b3e9b74c8 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -12,22 +12,19 @@ defined( 'ABSPATH' ) || exit; -use WooCommerce\Facebook\Utilities\Heartbeat; - /** * Abstract class AbstractFeed * - * Provides the base functionality for handling product feed requests and generation for Facebook integration. + * Provides the base functionality for handling Metadata feed requests and generation for Facebook integration. * This class defines the structure and common methods that must be implemented by any concrete feed class. * - * @package WooCommerce\Facebook\ProductFeed + * @package WooCommerce\Facebook\Feed * @since 3.5.0 */ abstract class AbstractFeed { /** The action callback for generating a feed */ const GENERATE_FEED_ACTION = 'wc_facebook_regenerate_feed_'; - - /** The action slug for getting the product feed */ + /** The action slug for getting the feed */ const REQUEST_FEED_ACTION = 'wc_facebook_get_feed_data_'; /** The action slug for triggering file upload */ const FEED_GEN_COMPLETE_ACTION = 'wc_facebook_feed_generation_completed_'; @@ -93,9 +90,13 @@ private function add_hooks( string $heartbeat ) { abstract public function schedule_feed_generation(); /** - * Regenerates the product feed. + * Schedules the feed generation immediately, ignoring the interval. * - * This method is responsible for initiating the regeneration of the product feed. + * @since 3.5.0 + */ + abstract public function schedule_feed_generation_immediately(); + + /** * The method ensures that the feed is regenerated based on the defined schedule. * * @since 3.5.0 @@ -103,8 +104,6 @@ abstract public function schedule_feed_generation(); abstract public function regenerate_feed(); /** - * Trigger the upload flow - * * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload * * @since 3.5.0 @@ -114,36 +113,22 @@ abstract public function send_request_to_upload_feed(); /** * Handles the feed data request. * - * This method must be implemented by the concrete feed class. - * * @since 3.5.0 */ abstract public function handle_feed_data_request(); - /** Schedules the feed generation immediately, ignoring the interval. - * - * This method must be implemented by the concrete feed class. - * - * @since 3.5.0 - */ - abstract public function schedule_feed_generation_immediately(); - /** - * Gets the URL for retrieving the product feed data. + * Gets the URL for retrieving the feed data. * - * This method must be implemented by the concrete feed class. - * - * @return string The URL for retrieving the product feed data. + * @return string The URL for retrieving the feed data. * @since 3.5.0 */ abstract public static function get_feed_data_url(): string; /** - * Gets the secret value that should be included in the ProductFeed URL. - * - * This method must be implemented by the concrete feed class. + * Gets the secret value/ token that should be included in the feed URL. * - * @return string The secret value for the ProductFeed URL. + * @return string The secret value for the feed URL. * @since 3.5.0 */ abstract public static function get_feed_secret(): string; diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 6b210ca92..101e29a2a 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -50,6 +50,15 @@ public function schedule_feed_generation() { // TODO: Implement schedule_feed_generation() method. } + /** + * Allows an admin to schedule the feed generation immediately. + * + * @since 3.5.0 + */ + public function schedule_feed_generation_immediately() { + // TODO: Implement schedule_feed_generation_immediately() method. + } + /** * Regenerates the example feed based on the defined schedule. * @@ -85,15 +94,6 @@ public function handle_feed_data_request() { // TODO: Implement handle_feed_data_request() method. } - /** - * Allows an admin to schedule the feed generation immediately. - * - * @since 3.5.0 - */ - public function schedule_feed_generation_immediately() { - // TODO: Implement schedule_feed_generation_immediately() method. - } - /** * Gets the URL for retrieving the feed data. * diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php index 74e8ed040..2a9cea3d8 100644 --- a/includes/Feed/ExampleFeedHandler.php +++ b/includes/Feed/ExampleFeedHandler.php @@ -17,7 +17,7 @@ * * Extends the FeedHandler interface to handle example feed file generation. * - * @package WooCommerce\Facebook\ProductFeed + * @package WooCommerce\Facebook\Feed * @since 3.5.0 */ class ExampleFeedHandler implements FeedHandler { @@ -43,6 +43,7 @@ public function __construct( FeedFileWriter $feed_writer ) { * Generate the feed file. * * This method is responsible for generating a feed file. + * * @since 3.5.0 */ public function generate_feed_file() { diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index 6321eef81..413e69677 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -122,6 +122,6 @@ public function get_plugin_name(): string { * @since 3.5.0 */ protected function get_batch_size(): int { - return - 1; + return -1; } } diff --git a/includes/Feed/FeedManager.php b/includes/Feed/FeedManager.php index 03173cff8..00669fce7 100644 --- a/includes/Feed/FeedManager.php +++ b/includes/Feed/FeedManager.php @@ -7,6 +7,7 @@ /** * Responsible for creating and managing feeds. * Global manipulations of the feed such as updating feed and upload ID to be made through this class. + * * @since 3.5.0 */ class FeedManager { @@ -75,7 +76,7 @@ public static function get_feed_types(): array { * @param string $data_stream_name The name of the data stream. * * @return FeedFileWriter - * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType + * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType. * @since 3.5.0 */ public static function get_feed_file_writer( string $data_stream_name ): FeedFileWriter { From 1740f23e0ee6ca13e4c7dbb514769fc950c3719f Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Fri, 21 Feb 2025 12:28:36 -0800 Subject: [PATCH 24/38] Implement Example Feed --- .../API/CommonFeedUploads/Create/Request.php | 8 +- .../API/CommonFeedUploads/Create/Response.php | 2 +- includes/Feed/AbstractFeed.php | 31 +++- includes/Feed/ExampleFeed.php | 141 +++++++++++++++--- includes/Feed/ExampleFeedHandler.php | 10 ++ 5 files changed, 163 insertions(+), 29 deletions(-) diff --git a/includes/API/CommonFeedUploads/Create/Request.php b/includes/API/CommonFeedUploads/Create/Request.php index ea496b1f3..d16bece7a 100644 --- a/includes/API/CommonFeedUploads/Create/Request.php +++ b/includes/API/CommonFeedUploads/Create/Request.php @@ -16,11 +16,11 @@ class Request extends ApiRequest { * Constructs the request. * * @param string $cpi_id Commerce Partner Integration ID. - * @param array $data Feed Metadata for File Update Post endpoint. - * Todo: add since + * @param array $data Feed Metadata for File Update Post endpoint. + * @since 3.5.0 */ - public function __construct( string $cpi_id, array $data ) { - parent::__construct( $cpi_id . '/file_update', 'POST' ); + public function __construct( array $data ) { + parent::__construct( '24316596247984028/file_update', 'POST' ); parent::set_data( $data ); } } diff --git a/includes/API/CommonFeedUploads/Create/Response.php b/includes/API/CommonFeedUploads/Create/Response.php index 1666cc1ef..9f6fc7ecc 100644 --- a/includes/API/CommonFeedUploads/Create/Response.php +++ b/includes/API/CommonFeedUploads/Create/Response.php @@ -9,6 +9,6 @@ /** * Response object for Common Feed Upload - * Todo: add since + * @since 3.5.0 */ class Response extends ApiResponse {} diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index b3e9b74c8..55d557072 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -28,6 +28,8 @@ abstract class AbstractFeed { const REQUEST_FEED_ACTION = 'wc_facebook_get_feed_data_'; /** The action slug for triggering file upload */ const FEED_GEN_COMPLETE_ACTION = 'wc_facebook_feed_generation_completed_'; + /** The commerce partner integration ID used in GraphPartnerIntegrationFileUpdatePost call */ + const COMMERCE_PARTNER_INTEGRATION_ID = '24316596247984028'; /** * The name of the data stream to be synced via this feed. @@ -136,12 +138,35 @@ abstract public static function get_feed_secret(): string; /** * Modifies the action name by appending the data stream name. * - * @param string $feed_name The base feed name. + * @param string $action_name The base feed name. * * @return string The modified action name. * @since 3.5.0 */ - protected static function modify_action_name( string $feed_name ): string { - return $feed_name . self::$data_stream_name; + protected static function modify_action_name( string $action_name ): string { + return $action_name . self::$data_stream_name; + } + + /** + * Checks whether fpassthru has been disabled in PHP. + * + * Helper method, do not open to public. + * + * @since 3.5.0 + * + * @return bool + */ + protected static function is_fpassthru_disabled(): bool { + $disabled = false; + if ( function_exists( 'ini_get' ) ) { + // phpcs:ignore + $disabled_functions = @ini_get( 'disable_functions' ); + + $disabled = + is_string( $disabled_functions ) && + //phpcs:ignore + in_array( 'fpassthru', explode( ',', $disabled_functions ), false ); + } + return $disabled; } } diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 101e29a2a..212437906 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -12,7 +12,10 @@ defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionScheduler; +use WooCommerce\Facebook\Framework\Api\Exception; +use WooCommerce\Facebook\Framework\Helper; use WooCommerce\Facebook\Utilities\Heartbeat; +use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException; /** * Example Feed class @@ -24,6 +27,9 @@ */ class ExampleFeed extends AbstractFeed { + const EXAMPLE_FEED_INTERVAL = 'wc_facebook_example_feed_generation_interval'; + const OPTION_FEED_URL_SECRET = 'wc_facebook_example_feed_url_secret'; + /** * Constructor. * @@ -41,13 +47,29 @@ public function __construct() { /** * Schedules the recurring feed generation. - * * This method must be implemented by the concrete feed class, usually by providing a recurring interval * * @since 3.5.0 */ public function schedule_feed_generation() { - // TODO: Implement schedule_feed_generation() method. + /** + * Filter the interval for generating the example feed. + * + * @param int $interval The interval in seconds. + * @since 3.5.0 + */ + $interval = apply_filters( self::EXAMPLE_FEED_INTERVAL, DAY_IN_SECONDS ); + + $schedule_action_hook_name = self::modify_action_name( self::GENERATE_FEED_ACTION ); + if ( ! as_next_scheduled_action( $schedule_action_hook_name ) ) { + as_schedule_recurring_action( + time(), + max( 2, $interval ), + $schedule_action_hook_name, + array(), + facebook_for_woocommerce()->get_id_dasherized() + ); + } } /** @@ -56,7 +78,10 @@ public function schedule_feed_generation() { * @since 3.5.0 */ public function schedule_feed_generation_immediately() { - // TODO: Implement schedule_feed_generation_immediately() method. + $schedule_action_hook_name = self::modify_action_name( self::GENERATE_FEED_ACTION ); + if ( ! as_next_scheduled_action( $schedule_action_hook_name ) ) { + $this->regenerate_feed(); + } } /** @@ -75,48 +100,122 @@ public function regenerate_feed() { /** * Trigger the upload flow - * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload + * Once feed regenerated, trigger upload via create_upload API + * This will hit the url defined in the class and trigger the handle streaming file * * @since 3.5.0 */ public function send_request_to_upload_feed() { - // TODO: Implement send_request_to_upload_feed() method. + $data = array( + 'url' => self::get_feed_data_url(), + 'feed_type' => 'PRODUCT_RATINGS_AND_REVIEWS', + 'update_type' => 'CREATE', + ); + + try { + facebook_for_woocommerce() + ->get_api() + ->create_common_upload( self::COMMERCE_PARTNER_INTEGRATION_ID, $data ); + } catch ( Exception $e ) { + // Log the error and continue. + facebook_for_woocommerce()->log( 'Failed to create example feed upload request: ' . $e->getMessage() ); + } } /** - * Handles the feed data request. - * - * This method must be implemented by the concrete feed class. + * Callback function that streams the feed file to the GraphPartnerIntegrationFileUpdatePost + * Ex: https://your-site-url.com/?wc-api=wc_facebook_get_feed_data_example&secret=your_generated_secret + * The above WooC Legacy REST API will trigger the handle_feed_data_request method + * See LegacyRequestApiStub.php for more details * * @since 3.5.0 */ public function handle_feed_data_request() { - // TODO: Implement handle_feed_data_request() method. + \WC_Facebookcommerce_Utils::log( 'ExampleFeed: Meta is requesting feed file.' ); + + $file_path = $this->feed_handler->get_feed_writer()->get_file_path(); + + // regenerate if the file doesn't exist. + if ( ! file_exists( $file_path ) ) { + $this->feed_handler->generate_feed_file(); + } + + try { + // bail early if the feed secret is not included or is not valid. + if ( self::get_feed_secret() !== Helper::get_requested_value( 'secret' ) ) { + throw new PluginException( 'ExampleFeed: Invalid secret provided.', 401 ); + } + + // bail early if the file can't be read. + if ( ! is_readable( $file_path ) ) { + throw new PluginException( 'ExampleFeed: File at path ' . $file_path . ' is not readable.', 404 ); + } + + // set the download headers. + header( 'Content-Type: text/csv; charset=utf-8' ); + header( 'Content-Description: File Transfer' ); + header( 'Content-Disposition: attachment; filename="' . basename( $file_path ) . '"' ); + header( 'Expires: 0' ); + header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' ); + header( 'Pragma: public' ); + header( 'Content-Length:' . filesize( $file_path ) ); + + // phpcs:ignore + $file = @fopen( $file_path, 'rb' ); + if ( ! $file ) { + throw new PluginException( 'ExampleFeed: Could not open feed file.', 500 ); + } + + // fpassthru might be disabled in some hosts (like Flywheel). + // phpcs:ignore + if ( $this->is_fpassthru_disabled() || ! @fpassthru( $file ) ) { + \WC_Facebookcommerce_Utils::log( 'ExampleFeed: fpassthru is disabled: getting file contents' ); + //phpcs:ignore + $contents = @stream_get_contents( $file ); + if ( ! $contents ) { + throw new PluginException( 'Could not get feed file contents.', 500 ); + } + echo $contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } catch ( \Exception $exception ) { + \WC_Facebookcommerce_Utils::log( 'ExampleFeed: Could not serve feed. ' . $exception->getMessage() . ' (' . $exception->getCode() . ')' ); + status_header( $exception->getCode() ); + } + exit; } /** - * Gets the URL for retrieving the feed data. + * Gets the URL for retrieving the product feed data using legacy WooCommerce REST API. + * Sample url: + * https://your-site-url.com/?wc-api=wc_facebook_get_feed_data_example&secret=your_generated_secret * - * This method must be implemented by the concrete feed class. - * - * @return string The URL for retrieving the feed data. * @since 3.5.0 + * @return string */ public static function get_feed_data_url(): string { - // TODO: Implement get_feed_data_url() method. - return ''; + $query_args = array( + 'wc-api' => self::modify_action_name( self::REQUEST_FEED_ACTION ), + 'secret' => self::get_feed_secret(), + ); + // phpcs:ignore + // nosemgrep: audit.php.wp.security.xss.query-arg + return add_query_arg( $query_args, home_url( '/' ) ); } + /** - * Gets the secret value that should be included in the ExampleFeed URL. - * - * This method must be implemented by the concrete feed class. + * Gets the secret value that should be included in the legacy WooCommerce REST API URL. * - * @return string The secret value for the ExampleFeed URL. * @since 3.5.0 + * @return string */ public static function get_feed_secret(): string { - // TODO: Implement get_feed_secret() method. - return ''; + $secret = get_option( self::OPTION_FEED_URL_SECRET, '' ); + if ( ! $secret ) { + $secret = wp_hash( 'example-feed-' . time() ); + update_option( self::OPTION_FEED_URL_SECRET, $secret ); + } + + return $secret; } } diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php index 2a9cea3d8..8afe16aba 100644 --- a/includes/Feed/ExampleFeedHandler.php +++ b/includes/Feed/ExampleFeedHandler.php @@ -49,4 +49,14 @@ public function __construct( FeedFileWriter $feed_writer ) { public function generate_feed_file() { // TODO: Implement generate_feed_file() method. } + + /** + * Get the feed file writer instance. + * + * @return FeedFileWriter + * @since 3.5.0 + */ + public function get_feed_writer(): FeedFileWriter { + return $this->feed_writer; + } } From ee920459a758f15c0842338c7fe23ca33cfcd35f Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Mon, 24 Feb 2025 20:58:20 -0800 Subject: [PATCH 25/38] About halfway implementing feed generator and csv feed handler --- class-wc-facebookcommerce.php | 2 +- includes/Feed/AbstractFeed.php | 29 +------- includes/Feed/CsvFeedFileWriter.php | 96 +++++++++++++++++++++++--- includes/Feed/ExampleFeed.php | 16 +++-- includes/Feed/ExampleFeedGenerator.php | 47 +++++++++---- includes/Feed/FeedFileWriter.php | 12 +++- includes/Feed/FeedGenerator.php | 8 +++ includes/Feed/FeedHandler.php | 7 ++ includes/Feed/FeedManager.php | 25 ++----- includes/fbutils.php | 30 ++++++-- 10 files changed, 186 insertions(+), 86 deletions(-) diff --git a/class-wc-facebookcommerce.php b/class-wc-facebookcommerce.php index bdf518529..b414a0d0e 100644 --- a/class-wc-facebookcommerce.php +++ b/class-wc-facebookcommerce.php @@ -70,7 +70,7 @@ class WC_Facebookcommerce extends WooCommerce\Facebook\Framework\Plugin { private $product_feed; /** @var WooCommerce\Facebook\Feed\FeedManager Entrypoint and creates all other feeds */ - private $feed_manager; + public $feed_manager; /** @var Background_Handle_Virtual_Products_Variations instance */ protected $background_handle_virtual_products_variations; diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 55d557072..d3b593328 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -125,7 +125,7 @@ abstract public function handle_feed_data_request(); * @return string The URL for retrieving the feed data. * @since 3.5.0 */ - abstract public static function get_feed_data_url(): string; + abstract public function get_feed_data_url(): string; /** * Gets the secret value/ token that should be included in the feed URL. @@ -133,7 +133,7 @@ abstract public static function get_feed_data_url(): string; * @return string The secret value for the feed URL. * @since 3.5.0 */ - abstract public static function get_feed_secret(): string; + abstract public function get_feed_secret(): string; /** * Modifies the action name by appending the data stream name. @@ -143,30 +143,7 @@ abstract public static function get_feed_secret(): string; * @return string The modified action name. * @since 3.5.0 */ - protected static function modify_action_name( string $action_name ): string { + public static function modify_action_name( string $action_name ): string { return $action_name . self::$data_stream_name; } - - /** - * Checks whether fpassthru has been disabled in PHP. - * - * Helper method, do not open to public. - * - * @since 3.5.0 - * - * @return bool - */ - protected static function is_fpassthru_disabled(): bool { - $disabled = false; - if ( function_exists( 'ini_get' ) ) { - // phpcs:ignore - $disabled_functions = @ini_get( 'disable_functions' ); - - $disabled = - is_string( $disabled_functions ) && - //phpcs:ignore - in_array( 'fpassthru', explode( ',', $disabled_functions ), false ); - } - return $disabled; - } } diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index d516acfaa..8df839fd1 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -10,6 +10,8 @@ namespace WooCommerce\Facebook\Feed; +use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException; + defined( 'ABSPATH' ) || exit; /** @@ -20,22 +22,38 @@ * @since 3.5.0 */ class CsvFeedFileWriter implements FeedFileWriter { + /** Product catalog feed file directory inside the uploads folder @var string*/ + const UPLOADS_DIRECTORY = 'facebook_for_woocommerce'; + + /** Feed file name @var string*/ + const FILE_NAME = '%s_catalog_%s.csv'; + /** * Use the feed name to distinguish which folder to write to. * * @var string * @since 3.5.0 */ - private $feed_name; + private string $feed_name; + + /** + * Header row for the feed file. + * + * @var array + * @since 3.5.0 + */ + private string $header_row; /** * Constructor. * * @param string $feed_name The name of the feed. + * @param string $header_row The headers for the feed csv. * @since 3.5.0 */ - public function __construct( string $feed_name ) { - $this->feed_name = $feed_name; + public function __construct( string $feed_name, string $header_row ) { + $this->feed_name = $feed_name; + $this->header_row = $header_row; } /** @@ -63,7 +81,7 @@ public function create_files_to_protect_feed_directory() { * @since 3.5.0 */ public function get_file_path(): string { - return ''; + return "{$this->get_file_directory()}/{$this->get_file_name()}"; } @@ -74,7 +92,7 @@ public function get_file_path(): string { * @since 3.5.0 */ public function get_temp_file_path(): string { - return ''; + return "{$this->get_file_directory()}/{$this->get_temp_file_name()}"; } /** @@ -84,7 +102,8 @@ public function get_temp_file_path(): string { * @since 3.5.0 */ public function get_file_directory(): string { - return ''; + $uploads_directory = wp_upload_dir( null, false ); + return trailingslashit( $uploads_directory['basedir'] ) . self::UPLOADS_DIRECTORY; } @@ -95,18 +114,73 @@ public function get_file_directory(): string { * @since 3.5.0 */ public function get_file_name(): string { - return ''; + $feed_secret = facebook_for_woocommerce()->feed_manager->get_feed_secret( $this->feed_name ); + return sprintf( self::FILE_NAME, $this->feed_name, wp_hash( $feed_secret ) ); } /** * Gets the temporary feed file name. * - * @param string $secret The secret used to generate the file name. - * * @return string * @since 3.5.0 */ - public function get_temp_file_name( string $secret ): string { - return $secret; + public function get_temp_file_name(): string { + $feed_secret = facebook_for_woocommerce()->feed_manager->get_feed_secret( $this->feed_name ); + return sprintf( self::FILE_NAME, $this->feed_name, 'temp_' . wp_hash( $feed_secret ) ); + } + + /** + * 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. + * @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 -- current product feed does not use Wordpress 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 feed file for writing.', 'facebook-for-woocommerce' ), 500 ); + } + + $file_path = $this->get_file_path(); + + // Check if we will be able to write to the final feed file. + //phpcs:ignore -- current product feed does not use Wordpress 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 the product catalog feed file for writing', 'facebook-for-woocommerce' ), 500 ); + } + + //phpcs:ignore -- current product feed does not use Wordpress file i/o functions + fwrite( $temp_feed_file, $this->header_row); + return $temp_feed_file; + } + + /** + * Rename temporary feed file into the final feed file. + * This is the last step fo the feed generation procedure. + * + * @since 3.5.0 + * @throws PluginException If the temporary feed file could not be renamed. + */ + public function promote_temp_file() { + $file_path = $this->get_file_path(); + $temp_file_path = $this->get_temp_file_path(); + if ( ! empty( $temp_file_path ) && ! empty( $file_path ) ) { + + // phpcs:ignore -- current product feed does not use Wordpress 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 rename the product catalog feed file', 'facebook-for-woocommerce' ), 500 ); + } + } } } diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 212437906..13034a443 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -36,13 +36,17 @@ class ExampleFeed extends AbstractFeed { * @since 3.5.0 */ public function __construct() { - $data_stream_name = FeedManager::EXAMPLE; - $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $data_stream_name ) ); + // Using the headers for ratings and reviews for this proof of concept. + $header = 'aggregator,store.name,store.id,store.store_urls,review_id,rating,title,content,created_at,' . + 'incentivized,has_verified_purchase,language,reviewer.name,reviewer.is_anonymous,product.name,product.url,' . + 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; + + $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( FeedManager::EXAMPLE, $header ) ); $scheduler = new ActionScheduler(); $this->feed_generator = new ExampleFeedGenerator( $scheduler, $this->feed_handler ); $this->feed_generator->init(); - parent::__construct( $data_stream_name, Heartbeat::HOURLY ); + parent::__construct( FeedManager::EXAMPLE, Heartbeat::HOURLY ); } /** @@ -168,7 +172,7 @@ public function handle_feed_data_request() { // fpassthru might be disabled in some hosts (like Flywheel). // phpcs:ignore - if ( $this->is_fpassthru_disabled() || ! @fpassthru( $file ) ) { + if ( \WC_Facebookcommerce_Utils::is_fpassthru_disabled() || ! @fpassthru( $file ) ) { \WC_Facebookcommerce_Utils::log( 'ExampleFeed: fpassthru is disabled: getting file contents' ); //phpcs:ignore $contents = @stream_get_contents( $file ); @@ -192,7 +196,7 @@ public function handle_feed_data_request() { * @since 3.5.0 * @return string */ - public static function get_feed_data_url(): string { + public function get_feed_data_url(): string { $query_args = array( 'wc-api' => self::modify_action_name( self::REQUEST_FEED_ACTION ), 'secret' => self::get_feed_secret(), @@ -209,7 +213,7 @@ public static function get_feed_data_url(): string { * @since 3.5.0 * @return string */ - public static function get_feed_secret(): string { + public function get_feed_secret(): string { $secret = get_option( self::OPTION_FEED_URL_SECRET, '' ); if ( ! $secret ) { $secret = wp_hash( 'example-feed-' . time() ); diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php index e58b87124..f9fc5e71f 100644 --- a/includes/Feed/ExampleFeedGenerator.php +++ b/includes/Feed/ExampleFeedGenerator.php @@ -2,7 +2,10 @@ namespace WooCommerce\Facebook\Feed; +use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionSchedulerInterface; +use Automattic\WooCommerce\ActionSchedulerJobFramework\Utilities\BatchQueryOffset; use WC_Facebookcommerce; +use WooCommerce\Facebook\Jobs\LoggingTrait; defined( 'ABSPATH' ) || exit; @@ -15,6 +18,24 @@ * @since 3.5.0 */ class ExampleFeedGenerator extends FeedGenerator { + /** + * Used to interact with the directory system. + * + * @var FeedFileWriter $feed_writer + */ + private FeedFileWriter $feed_writer; + + /** + * Constructor for this instance. + * + * @param ActionSchedulerInterface $action_scheduler Global scheduler. + * @param FeedHandler $feed_handler The feed handler instance for this feed. + */ + public function __construct( ActionSchedulerInterface $action_scheduler, FeedHandler $feed_handler ) { + parent::__construct( $action_scheduler, $feed_handler ); + $this->feed_writer = $feed_handler->get_feed_writer(); + } + /** * Handles the start of the feed generation process. * @@ -22,15 +43,26 @@ class ExampleFeedGenerator extends FeedGenerator { * @since 3.5.0 */ protected function handle_start() { + $this->feed_writer->create_files_to_protect_feed_directory(); + $this->feed_writer->prepare_temporary_feed_file(); } /** * Handles the end of the feed generation process. * * @inheritdoc + * @throw PluginException If the temporary file cannot be promoted. * @since 3.5.0 */ protected function handle_end() { + $this->feed_writer->promote_temp_file(); + + /** + * Trigger upload from ExampleFeed instance + * + * @since 3.5.0 + */ + do_action( ExampleFeed::modify_action_name( ExampleFeed::FEED_GEN_COMPLETE_ACTION ) ); } /** @@ -76,18 +108,7 @@ protected function process_item( $item, array $args ) { * @since 3.5.0 */ public function get_name(): string { - return ''; - } - - /** - * Gets the plugin name associated with the feed generator. - * - * @return string The plugin name. - * @inheritdoc - * @since 3.5.0 - */ - public function get_plugin_name(): string { - return WC_Facebookcommerce::PLUGIN_ID; + return self::class; } /** @@ -97,6 +118,6 @@ public function get_plugin_name(): string { * @inheritdoc */ protected function get_batch_size(): int { - return -1; + return 15; } } diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php index 534de5c07..4d3fa9fbd 100644 --- a/includes/Feed/FeedFileWriter.php +++ b/includes/Feed/FeedFileWriter.php @@ -64,10 +64,16 @@ public function get_file_name(): string; /** * Gets the temporary feed file name. * - * @param string $secret The secret to use for the temporary file name. - * * @return string * @since 3.5.0 */ - public function get_temp_file_name( string $secret ): string; + public function get_temp_file_name(): string; + + /** + * Prepare a fresh empty temporary feed file with the header row. + * + * @return resource A file pointer resource. + * @since 3.5.0 + */ + public function prepare_temporary_feed_file(); } diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index 413e69677..d10faeb9a 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -1,4 +1,12 @@ feed_instances[ $feed_type ]; + public function get_feed_secret( string $feed_type ): string { + $instance = $this->feed_instances[ $feed_type ]; + return $instance->get_feed_secret(); } } diff --git a/includes/fbutils.php b/includes/fbutils.php index 48ad0e382..b5a9bc64d 100644 --- a/includes/fbutils.php +++ b/includes/fbutils.php @@ -864,10 +864,10 @@ public static function prepare_product_variation_data_items_batch( $product ) { public static function logExceptionImmediatelyToMeta(Throwable $error, array $context = []) { /** * WIP: This is a dummy function to send exception logs to Meta. - * $context is an array of data that will be sent to Meta, includes commerce_merchant_settings_id, + * $context is an array of data that will be sent to Meta, includes commerce_merchant_settings_id, * catalog_id, order_id, promotion_id, flow_name, flow_step, extra_data and etc. */ - + // TODO: Implement enqueue logging to Meta function. $response = null; @@ -880,16 +880,36 @@ public static function logExceptionImmediatelyToMeta(Throwable $error, array $co public static function logTelemetryToMeta(string $message, array $context = []) { /** * WIP: This is a dummy function to send telemetry logs to Meta. - * $context is an array of data that will be sent to Meta, includes commerce_merchant_settings_id, + * $context is an array of data that will be sent to Meta, includes commerce_merchant_settings_id, * catalog_id, order_id, promotion_id, flow_name, flow_step, extra_data and etc. */ - + // TODO: Implement push logging request to global message queue function. $response = null; - + return $response; } + /** + * Checks whether fpassthru has been disabled in PHP. + * + * @since 3.5.0 + * @return bool + */ + public static function is_fpassthru_disabled(): bool { + $disabled = false; + if ( function_exists( 'ini_get' ) ) { + // phpcs:ignore + $disabled_functions = @ini_get( 'disable_functions' ); + + $disabled = + is_string( $disabled_functions ) && + //phpcs:ignore + in_array( 'fpassthru', explode( ',', $disabled_functions ), false ); + } + return $disabled; + } + } endif; From 14611881d6f0cfa0a4afc898b6b95363e70c0186 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Tue, 25 Feb 2025 19:00:18 -0800 Subject: [PATCH 26/38] Finish implementing the example feed; todo: manual, unit, integration testing --- facebook-commerce.php | 13 ++- .../API/CommonFeedUploads/Create/Request.php | 13 ++- .../API/CommonFeedUploads/Create/Response.php | 10 ++ includes/Feed/AbstractFeed.php | 2 - includes/Feed/CsvFeedFileWriter.php | 93 +++++++++++++++++-- includes/Feed/ExampleFeed.php | 3 +- includes/Feed/ExampleFeedGenerator.php | 48 ++++++++-- includes/Feed/ExampleFeedHandler.php | 2 +- includes/Feed/FeedFileWriter.php | 14 +++ includes/Feed/FeedManager.php | 8 ++ 10 files changed, 185 insertions(+), 21 deletions(-) diff --git a/facebook-commerce.php b/facebook-commerce.php index ba6f83855..febf61b7a 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -870,7 +870,7 @@ private function save_product_settings( WC_Product $product ) { $woo_product->set_description( sanitize_text_field( wp_unslash( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ) ) ); $woo_product->set_rich_text_description( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) { $woo_product->set_price( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) ); } @@ -2434,6 +2434,17 @@ public function get_facebook_pixel_id() { return (string) apply_filters( 'wc_facebook_pixel_id', get_option( self::SETTING_FACEBOOK_PIXEL_ID, '' ), $this ); } + /** + * Retrieve the commerce partner integration ID used in GraphPartnerIntegrationFileUpdatePost call + * Hard coded for now. Update once Mice settings are available. + * + * @return string + * @since 3.5.0 + */ + public function get_commerce_partner_integration_id(): string { + return '24316596247984028'; + } + /** * Gets the IDs of the categories to be excluded from sync. * diff --git a/includes/API/CommonFeedUploads/Create/Request.php b/includes/API/CommonFeedUploads/Create/Request.php index d16bece7a..f8f8ada8c 100644 --- a/includes/API/CommonFeedUploads/Create/Request.php +++ b/includes/API/CommonFeedUploads/Create/Request.php @@ -1,4 +1,13 @@ prepare_temporary_feed_file(); + + // Step 2: Write products feed into the temporary feed file. + $this->write_temp_feed_file(); + + // Step 3: Rename temporary feed file to final feed file. + $this->promote_temp_file(); + } catch ( PluginException $e ) { + WC_Facebookcommerce_Utils::log( wp_json_encode( $e->getMessage() ) ); + // Close the temporary file. + if ( ! empty( $temp_feed_file ) && is_resource( $temp_feed_file ) ) { + fclose( $temp_feed_file ); //phpcs:ignore + } + + // Delete the temporary file. + if ( ! empty( $temp_file_path ) && file_exists( $temp_file_path ) ) { + unlink( $temp_file_path ); //phpcs:ignore + } + } + } + + /** + * Write the feed data to the temporary feed file. + * + * @since 3.5.0 + */ + public function write_temp_feed_file() { + /** + * Real implementation would get the necessary objects and turn them into a row to write to feed file. + * For POC, will just write to temp file. + */ + //phpcs:ignore -- current product feed does not use Wordpress file i/o functions + $temp_feed_file = fopen( $this->get_temp_file_path(), 'a' ); + if ( ! empty( $this->get_temp_file_path() ) ) { + fwrite( $temp_feed_file, $this->csv_data ); //phpcs:ignore + } + + if ( ! empty( $temp_feed_file ) ) { + fclose( $temp_feed_file ); //phpcs:ignore + } } /** - * Creates files in the given feed directory to prevent directory listing and hotlinking. + * Creates files in the feed directory to prevent directory listing and hotlinking. * * @since 3.5.0 */ public function create_files_to_protect_feed_directory() { + $feed_directory = trailingslashit( $this->get_file_directory() ); + + $files = array( + array( + 'base' => $feed_directory, + 'file' => 'index.html', + 'content' => '', + ), + array( + 'base' => $feed_directory, + 'file' => '.htaccess', + 'content' => 'deny from all', + ), + ); + + foreach ( $files as $file ) { + + if ( wp_mkdir_p( $file['base'] ) && ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) ) { + // phpcs:ignore -- current product feed does not use Wordpress file i/o functions + $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'w' ); + if ( $file_handle ) { + fwrite( $file_handle, $file['content'] ); //phpcs:ignore + fclose( $file_handle ); //phpcs:ignore + } + } + } } /** @@ -103,7 +182,7 @@ public function get_temp_file_path(): string { */ public function get_file_directory(): string { $uploads_directory = wp_upload_dir( null, false ); - return trailingslashit( $uploads_directory['basedir'] ) . self::UPLOADS_DIRECTORY; + return trailingslashit( $uploads_directory['basedir'] ) . sprintf( self::UPLOADS_DIRECTORY, $this->feed_name ); } diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 13034a443..7d2555fcf 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -117,9 +117,10 @@ public function send_request_to_upload_feed() { ); try { + $cpi_id = facebook_for_woocommerce()->get_integration()->get_commerce_partner_integration_id(); facebook_for_woocommerce() ->get_api() - ->create_common_upload( self::COMMERCE_PARTNER_INTEGRATION_ID, $data ); + ->create_common_upload( $cpi_id, $data ); } catch ( Exception $e ) { // Log the error and continue. facebook_for_woocommerce()->log( 'Failed to create example feed upload request: ' . $e->getMessage() ); diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php index f9fc5e71f..229e499b0 100644 --- a/includes/Feed/ExampleFeedGenerator.php +++ b/includes/Feed/ExampleFeedGenerator.php @@ -3,9 +3,6 @@ namespace WooCommerce\Facebook\Feed; use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionSchedulerInterface; -use Automattic\WooCommerce\ActionSchedulerJobFramework\Utilities\BatchQueryOffset; -use WC_Facebookcommerce; -use WooCommerce\Facebook\Jobs\LoggingTrait; defined( 'ABSPATH' ) || exit; @@ -51,7 +48,6 @@ protected function handle_start() { * Handles the end of the feed generation process. * * @inheritdoc - * @throw PluginException If the temporary file cannot be promoted. * @since 3.5.0 */ protected function handle_end() { @@ -62,7 +58,7 @@ protected function handle_end() { * * @since 3.5.0 */ - do_action( ExampleFeed::modify_action_name( ExampleFeed::FEED_GEN_COMPLETE_ACTION ) ); + do_action( ExampleFeed::modify_action_name( AbstractFeed::FEED_GEN_COMPLETE_ACTION ) ); } /** @@ -75,7 +71,33 @@ protected function handle_end() { * @since 3.5.0 */ protected function get_items_for_batch( int $batch_number, array $args ): array { - return array(); + // Complete implementation would do a query based on $batch_number and get_batch_size(). + // Example below. + /** + * $product_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT post.ID + FROM {$wpdb->posts} as post + LEFT JOIN {$wpdb->posts} as parent ON post.post_parent = parent.ID + WHERE + ( post.post_type = 'product_variation' AND parent.post_status = 'publish' ) + OR + ( post.post_type = 'product' AND post.post_status = 'publish' ) + ORDER BY post.ID ASC + LIMIT %d OFFSET %d", + $this->get_batch_size(), + $this->get_query_offset( $batch_number ) + ) + ); + */ + + // For proof of concept, we will just return the review id for batch 1 + // In parent classes, batch number starts with 1. + if ( 1 === $batch_number ) { + return array( 2, 3, 4 ); + } else { + return array(); + } } /** @@ -87,6 +109,16 @@ protected function get_items_for_batch( int $batch_number, array $args ): array * @since 3.5.0 */ protected function process_items( array $items, array $args ) { + // phpcs:ignore -- Using fopen to match existing implementation. + $temp_feed_file = fopen( $this->feed_writer->get_temp_file_path(), 'a' ); + // True override of write_feed_file would probably take an array of item ids or item objects + // For poc, will just write to the temp feed file. + $this->feed_writer->write_temp_feed_file(); + + if ( is_resource( $temp_feed_file ) ) { + //phpcs:ignore -- Using fclose to match existing implementation. + fclose( $temp_feed_file ); + } } /** @@ -98,6 +130,8 @@ protected function process_items( array $items, array $args ) { * @since 3.5.0 */ protected function process_item( $item, array $args ) { + // Needed to satisfy the class inheritance + // Because of the i/o opening and closing original feed implementation foregoes this method. } /** @@ -118,6 +152,6 @@ public function get_name(): string { * @inheritdoc */ protected function get_batch_size(): int { - return 15; + return 1; } } diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php index 8afe16aba..dff9d58e2 100644 --- a/includes/Feed/ExampleFeedHandler.php +++ b/includes/Feed/ExampleFeedHandler.php @@ -47,7 +47,7 @@ public function __construct( FeedFileWriter $feed_writer ) { * @since 3.5.0 */ public function generate_feed_file() { - // TODO: Implement generate_feed_file() method. + $this->feed_writer->write_feed_file(); } /** diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php index 4d3fa9fbd..6d9d085c1 100644 --- a/includes/Feed/FeedFileWriter.php +++ b/includes/Feed/FeedFileWriter.php @@ -76,4 +76,18 @@ public function get_temp_file_name(): string; * @since 3.5.0 */ public function prepare_temporary_feed_file(); + + /** + * Promote the temporary feed file to the final feed file. + * + * @since 3.5.0 + */ + public function promote_temp_file(); + + /** + * Write to the temp feed file. + * + * @since 3.5.0 + */ + public function write_temp_feed_file(); } diff --git a/includes/Feed/FeedManager.php b/includes/Feed/FeedManager.php index 5bb06b771..64807206b 100644 --- a/includes/Feed/FeedManager.php +++ b/includes/Feed/FeedManager.php @@ -1,4 +1,12 @@ Date: Wed, 26 Feb 2025 10:56:21 -0800 Subject: [PATCH 27/38] add throws tag --- includes/Feed/ExampleFeed.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 7d2555fcf..b108e98da 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -133,6 +133,7 @@ public function send_request_to_upload_feed() { * The above WooC Legacy REST API will trigger the handle_feed_data_request method * See LegacyRequestApiStub.php for more details * + * @throws PluginException If file issue comes up. * @since 3.5.0 */ public function handle_feed_data_request() { From 6fdd6e57b1a21898e8ce5c0acd6806489bc7520e Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Thu, 27 Feb 2025 15:27:44 -0800 Subject: [PATCH 28/38] Some fixes and some temp code for testing --- includes/API.php | 29 ++++++++++++- includes/Feed/AbstractFeed.php | 54 +++++++---------------- includes/Feed/CsvFeedFileWriter.php | 18 +++++++- includes/Feed/ExampleFeed.php | 60 +++++++++++++++++++++----- includes/Feed/ExampleFeedGenerator.php | 1 + includes/Feed/ExampleFeedHandler.php | 6 +++ includes/Feed/FeedFileWriter.php | 7 +++ includes/Feed/FeedManager.php | 4 +- includes/fbproductfeed.php | 1 + 9 files changed, 124 insertions(+), 56 deletions(-) diff --git a/includes/API.php b/includes/API.php index dd418f8d4..d3ffb7676 100644 --- a/includes/API.php +++ b/includes/API.php @@ -102,6 +102,32 @@ protected function perform_request( $request ): API\Response { return parent::perform_request( $request ); } + /** + * Using this to perform a hard coded API request for proof of concept + * Current use case: hitting GraphPartnerIntegrationFileUpdatePost with a Magento Store for POC + * Consider keeping: It will be useful to have the ability to hit the API from local without configuring new shop and getting permissions + * @param $request + * @param $access_token + * @return Response + * @throws Framework\Plugin\Exception + * @throws Request_Limit_Reached + */ + private function perform_stub_request($request, $access_token) : API\Response { + $current_token = $this->get_access_token(); + $this->set_access_token( $access_token ); + $rate_limit_id = $request::get_rate_limit_id(); + $delay_timestamp = $this->get_rate_limit_delay( $rate_limit_id ); + // if there is a delayed timestamp in the future, throw an exception + if ( $delay_timestamp >= time() ) { + $this->handle_throttled_request( $rate_limit_id, $delay_timestamp ); + } else { + $this->set_rate_limit_delay( $rate_limit_id, 0 ); + } + $response = parent::perform_request( $request ); + $this->set_access_token( $current_token ); + return $response; + } + /** * Validates a response after it has been parsed and instantiated. @@ -564,8 +590,7 @@ public function create_upload( string $product_feed_id, array $data ) { public function create_common_upload( string $cpi_id, array $data ): Response { $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); - - return $this->perform_request( $request ); + return $this->perform_stub_request( $request, 'EAACxonUmtyIBOzdmBUrXl4mlzZCpfwMpaenD2ZB1GC5Wmcu0nlQ4F35STxMERUAT2MZB89eTvJqhVQZAZAWnLFoyywCPtJf8h0lybhzV7Yp8cPy7VbtSXcgS1ww1TivJa2NCDFNtS48gSj76zXGhq7mjUeF5X3ZBppf1TwM5Np0J5FXopWcspDHiAaqYckZBjD9pikHZAyeD2wBpfspK7r1v0aAZA' ); } diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 280077dae..9967c743f 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -29,13 +29,19 @@ abstract class AbstractFeed { /** The action slug for triggering file upload */ const FEED_GEN_COMPLETE_ACTION = 'wc_facebook_feed_generation_completed_'; - /** - * The name of the data stream to be synced via this feed. - * - * @var string - * @since 3.5.0 - */ - private static string $data_stream_name; + /** Schedule feed generation on some interval hook name for children classes. */ + const SCHEDULE_LATER_CALL_BACK = 'schedule_feed_generation'; + /** Schedule an immediate file generator on the scheduler hook name. For testing mostly. */ + const SCHEDULE_IMMEDIATE_CALL_BACK = 'schedule_feed_generation_immediately'; + /** Make a new file for upload hook name for children classes. */ + const REGENERATE_CALL_BACK = 'regenerate_feed'; + /** Make upload call to Meta hook name for children classes. */ + const UPLOAD_CALL_BACK = 'send_request_to_upload_feed'; + /** Stream file to upload endpoint hook name for children classes. */ + const STREAM_CALL_BACK = 'handle_feed_data_request'; + /** Hook prefix for Legacy REST API hook name */ + const LEGACY_API_PREFIX = 'woocommerce_api_'; + /** * The feed generator instance for the given feed. @@ -53,33 +59,6 @@ abstract class AbstractFeed { */ protected FeedHandler $feed_handler; - /** - * Constructor. - * - * Initializes the feed with the given data stream name and adds the necessary hooks. - * - * @param string $data_stream_name The name of the data stream. - * @param string $heartbeat The heartbeat interval for the feed generation. - * @since 3.5.0 - */ - public function __construct( string $data_stream_name, string $heartbeat ) { - self::$data_stream_name = $data_stream_name; - $this->add_hooks( $heartbeat ); - } - - /** - * Adds the necessary hooks for feed generation and data request handling. - * - * @param string $heartbeat The heartbeat interval for the feed generation. - * @since 3.5.0 - */ - private function add_hooks( string $heartbeat ) { - add_action( $heartbeat, $this->schedule_feed_generation() ); - add_action( self::modify_action_name( self::GENERATE_FEED_ACTION ), $this->regenerate_feed() ); - add_action( self::modify_action_name( self::FEED_GEN_COMPLETE_ACTION ), $this->send_request_to_upload_feed() ); - add_action( 'woocommerce_api_' . self::modify_action_name( self::REQUEST_FEED_ACTION ), $this->handle_feed_data_request() ); - } - /** * Schedules the recurring feed generation. * @@ -136,12 +115,9 @@ abstract public function get_feed_secret(): string; /** * Modifies the action name by appending the data stream name. * - * @param string $action_name The base feed name. - * + * @param string $action_name The name of the hook. * @return string The modified action name. * @since 3.5.0 */ - public static function modify_action_name( string $action_name ): string { - return $action_name . self::$data_stream_name; - } + abstract public static function modify_action_name( string $action_name ): string; } diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index 5ee7a82a3..153be11df 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -75,7 +75,9 @@ public function __construct( string $feed_name, string $header_row ) { */ public function write_feed_file() { try { - // if not temporary, we are writing the whole file in the original flow, not in the generator. + $this->create_feed_directory(); + $this->create_files_to_protect_feed_directory(); + // Step 1: Prepare the temporary empty feed file with header row. $temp_feed_file = $this->prepare_temporary_feed_file(); @@ -98,6 +100,20 @@ public function write_feed_file() { } } + /** + * Generates the product catalog feed file. + * + * @throws PluginException If the directory could not be created. + * @since 3.5.0 + */ + public function create_feed_directory() { + $directory_created = wp_mkdir_p( $this->get_file_directory() ); + if ( ! $directory_created ) { + //phpcs:ignore -- Escaping function for translated string not available in this context + throw new PluginException( __( 'Could not create product catalog feed directory', 'facebook-for-woocommerce' ), 500 ); + } + } + /** * Write the feed data to the temporary feed file. * diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index b108e98da..eb8c85923 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -30,23 +30,43 @@ class ExampleFeed extends AbstractFeed { const EXAMPLE_FEED_INTERVAL = 'wc_facebook_example_feed_generation_interval'; const OPTION_FEED_URL_SECRET = 'wc_facebook_example_feed_url_secret'; + /** Name of datafeed for modifying action names. + * + * @var string + */ + private string $data_stream_name; + /** * Constructor. * * @since 3.5.0 */ public function __construct() { + $this->data_stream_name = FeedManager::EXAMPLE; // Using the headers for ratings and reviews for this proof of concept. $header = 'aggregator,store.name,store.id,store.store_urls,review_id,rating,title,content,created_at,' . 'incentivized,has_verified_purchase,language,reviewer.name,reviewer.is_anonymous,product.name,product.url,' . 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; - $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( FeedManager::EXAMPLE, $header ) ); + $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $this->data_stream_name, $header ) ); $scheduler = new ActionScheduler(); $this->feed_generator = new ExampleFeedGenerator( $scheduler, $this->feed_handler ); $this->feed_generator->init(); + $this->add_hooks( Heartbeat::HOURLY ); + } - parent::__construct( FeedManager::EXAMPLE, Heartbeat::HOURLY ); + /** + * Adds the necessary hooks for feed generation and data request handling. + * + * @param string $heartbeat The heartbeat interval for the feed generation. + * @since 3.5.0 + */ + protected function add_hooks( string $heartbeat ) { + add_action( $heartbeat, array( $this, self::SCHEDULE_IMMEDIATE_CALL_BACK ) ); + add_action( self::modify_action_name( self::GENERATE_FEED_ACTION ), array( $this, self::REGENERATE_CALL_BACK ) ); + add_action( self::modify_action_name( self::FEED_GEN_COMPLETE_ACTION ), array( $this, self::UPLOAD_CALL_BACK ) ); + add_action( self::LEGACY_API_PREFIX . self::modify_action_name( self::REQUEST_FEED_ACTION ), array( $this, self::STREAM_CALL_BACK ) ); + add_action( 'schedule_immediate_example_file', array( $this, 'schedule_feed_generation_immediately' ) ); } /** @@ -71,7 +91,7 @@ public function schedule_feed_generation() { max( 2, $interval ), $schedule_action_hook_name, array(), - facebook_for_woocommerce()->get_id_dasherized() + \WC_Facebookcommerce::instance()->get_id_dasherized() ); } } @@ -83,9 +103,13 @@ public function schedule_feed_generation() { */ public function schedule_feed_generation_immediately() { $schedule_action_hook_name = self::modify_action_name( self::GENERATE_FEED_ACTION ); - if ( ! as_next_scheduled_action( $schedule_action_hook_name ) ) { - $this->regenerate_feed(); - } + as_schedule_recurring_action( + time(), + 600, + $schedule_action_hook_name, + array(), + \WC_Facebookcommerce::instance()->get_id_dasherized() + ); } /** @@ -95,7 +119,7 @@ public function schedule_feed_generation_immediately() { */ public function regenerate_feed() { // Maybe use new ( experimental ), feed generation framework. - if ( facebook_for_woocommerce()->get_integration()->is_new_style_feed_generation_enabled() ) { + if ( \WC_Facebookcommerce::instance()->get_integration()->is_new_style_feed_generation_enabled() ) { $this->feed_generator->queue_start(); } else { $this->feed_handler->generate_feed_file(); @@ -117,13 +141,13 @@ public function send_request_to_upload_feed() { ); try { - $cpi_id = facebook_for_woocommerce()->get_integration()->get_commerce_partner_integration_id(); - facebook_for_woocommerce() + $cpi_id = \WC_Facebookcommerce::instance()->get_integration()->get_commerce_partner_integration_id(); + \WC_Facebookcommerce::instance() ->get_api() ->create_common_upload( $cpi_id, $data ); } catch ( Exception $e ) { // Log the error and continue. - facebook_for_woocommerce()->log( 'Failed to create example feed upload request: ' . $e->getMessage() ); + \WC_Facebookcommerce::instance()->log( 'Failed to create example feed upload request: ' . $e->getMessage() ); } } @@ -216,12 +240,26 @@ public function get_feed_data_url(): string { * @return string */ public function get_feed_secret(): string { + /* //phpcs:ignore $secret = get_option( self::OPTION_FEED_URL_SECRET, '' ); if ( ! $secret ) { $secret = wp_hash( 'example-feed-' . time() ); update_option( self::OPTION_FEED_URL_SECRET, $secret ); } - return $secret; + return $secret;*/ + return 'secret'; + } + + /** + * Modifies the action name by appending the data stream name. + * + * @param string $action_name The name of the hook. + * @return string The modified action name. + * @since 3.5.0 + */ + public static function modify_action_name( string $action_name ): string { + $name = FeedManager::EXAMPLE; + return "{$action_name}{$name}"; } } diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php index 229e499b0..62765b73a 100644 --- a/includes/Feed/ExampleFeedGenerator.php +++ b/includes/Feed/ExampleFeedGenerator.php @@ -40,6 +40,7 @@ public function __construct( ActionSchedulerInterface $action_scheduler, FeedHan * @since 3.5.0 */ protected function handle_start() { + // Create directory if not available and then the files to protect the directory. $this->feed_writer->create_files_to_protect_feed_directory(); $this->feed_writer->prepare_temporary_feed_file(); } diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php index dff9d58e2..4d0a80b9d 100644 --- a/includes/Feed/ExampleFeedHandler.php +++ b/includes/Feed/ExampleFeedHandler.php @@ -48,6 +48,12 @@ public function __construct( FeedFileWriter $feed_writer ) { */ public function generate_feed_file() { $this->feed_writer->write_feed_file(); + /** + * Trigger upload from ExampleFeed instance + * + * @since 3.5.0 + */ + do_action( ExampleFeed::modify_action_name( AbstractFeed::FEED_GEN_COMPLETE_ACTION ) ); } /** diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php index 6d9d085c1..cffd1f834 100644 --- a/includes/Feed/FeedFileWriter.php +++ b/includes/Feed/FeedFileWriter.php @@ -20,6 +20,13 @@ interface FeedFileWriter { */ public function write_feed_file(); + /** + * Create feed directory. + * + * @since 3.5.0 + */ + public function create_feed_directory(); + /** * Creates files in the catalog feed directory to prevent directory listing and hotlinking. * diff --git a/includes/Feed/FeedManager.php b/includes/Feed/FeedManager.php index 64807206b..f54e0d447 100644 --- a/includes/Feed/FeedManager.php +++ b/includes/Feed/FeedManager.php @@ -10,8 +10,6 @@ namespace WooCommerce\Facebook\Feed; -use WooCommerce\Facebook\Utilities\Heartbeat; - /** * Responsible for creating and managing feeds. * Global manipulations of the feed such as updating feed and upload ID to be made through this class. @@ -62,7 +60,7 @@ public function __construct() { private function create_feed( string $data_stream_name ): AbstractFeed { switch ( $data_stream_name ) { case self::EXAMPLE: - return new ExampleFeed( $data_stream_name, Heartbeat::HOURLY ); + return new ExampleFeed(); default: throw new \InvalidArgumentException( 'Invalid data stream name' ); } diff --git a/includes/fbproductfeed.php b/includes/fbproductfeed.php index 4f14f625c..1f59b754c 100644 --- a/includes/fbproductfeed.php +++ b/includes/fbproductfeed.php @@ -202,6 +202,7 @@ public function generate_productfeed_file() { /** * Creates files in the catalog feed directory to prevent directory listing and hotlinking. + * Will create directory if not already available * * @since 1.11.0 */ From 222a9c5b70eac28c3233021f536629a849879364 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Sun, 2 Mar 2025 22:52:46 -0800 Subject: [PATCH 29/38] Remove hard coding, but add comments on how to test --- includes/API.php | 5 ++- includes/Feed/AbstractFeed.php | 11 +------ includes/Feed/CsvFeedFileWriter.php | 6 ++-- includes/Feed/ExampleFeed.php | 47 +++++++++-------------------- package-lock.json | 16 +++++----- 5 files changed, 31 insertions(+), 54 deletions(-) diff --git a/includes/API.php b/includes/API.php index d3ffb7676..60dc6915e 100644 --- a/includes/API.php +++ b/includes/API.php @@ -115,6 +115,8 @@ protected function perform_request( $request ): API\Response { private function perform_stub_request($request, $access_token) : API\Response { $current_token = $this->get_access_token(); $this->set_access_token( $access_token ); + $this->request_headers['Authorization'] = "Bearer {$access_token}"; + $rate_limit_id = $request::get_rate_limit_id(); $delay_timestamp = $this->get_rate_limit_delay( $rate_limit_id ); // if there is a delayed timestamp in the future, throw an exception @@ -125,6 +127,7 @@ private function perform_stub_request($request, $access_token) : API\Response { } $response = parent::perform_request( $request ); $this->set_access_token( $current_token ); + $this->request_headers['Authorization'] = "Bearer {$current_token}"; return $response; } @@ -590,7 +593,7 @@ public function create_upload( string $product_feed_id, array $data ) { public function create_common_upload( string $cpi_id, array $data ): Response { $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); - return $this->perform_stub_request( $request, 'EAACxonUmtyIBOzdmBUrXl4mlzZCpfwMpaenD2ZB1GC5Wmcu0nlQ4F35STxMERUAT2MZB89eTvJqhVQZAZAWnLFoyywCPtJf8h0lybhzV7Yp8cPy7VbtSXcgS1ww1TivJa2NCDFNtS48gSj76zXGhq7mjUeF5X3ZBppf1TwM5Np0J5FXopWcspDHiAaqYckZBjD9pikHZAyeD2wBpfspK7r1v0aAZA' ); + return $this->perform_stub_request( $request,'EAACxonUmtyIBO9ovBryXe2LxuFC3QThLhmpWkSvIk2UoHS3WixeGESCd3HhzZAyKB3E0F6gvoJJxyKgLRSSQvcpDbbAV17E7vtZCBDp4Q9vFzVFyWMUFEZBvohBIaPbK9813ooqhAsuXOL2a27BgLd6G4FHrO75nPeEouGFJheMWJ3ZC9vBLyXfEEX6WOZB2F0yvjuKC6CNGMcqolDieZALkum' ); } diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 9967c743f..6db26056c 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -30,10 +30,8 @@ abstract class AbstractFeed { const FEED_GEN_COMPLETE_ACTION = 'wc_facebook_feed_generation_completed_'; /** Schedule feed generation on some interval hook name for children classes. */ - const SCHEDULE_LATER_CALL_BACK = 'schedule_feed_generation'; + const SCHEDULE_CALL_BACK = 'schedule_feed_generation'; /** Schedule an immediate file generator on the scheduler hook name. For testing mostly. */ - const SCHEDULE_IMMEDIATE_CALL_BACK = 'schedule_feed_generation_immediately'; - /** Make a new file for upload hook name for children classes. */ const REGENERATE_CALL_BACK = 'regenerate_feed'; /** Make upload call to Meta hook name for children classes. */ const UPLOAD_CALL_BACK = 'send_request_to_upload_feed'; @@ -68,13 +66,6 @@ abstract class AbstractFeed { */ abstract public function schedule_feed_generation(); - /** - * Schedules the feed generation immediately, ignoring the interval. - * - * @since 3.5.0 - */ - abstract public function schedule_feed_generation_immediately(); - /** * The method ensures that the feed is regenerated based on the defined schedule. * diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index 153be11df..429ba7f9c 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -51,9 +51,9 @@ class CsvFeedFileWriter implements FeedFileWriter { * @var string * @since 3.5.0 */ - private string $csv_data = "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"2\",\"5\",\"Great product\",\"Very happy with this purchase. Would buy again.\",\"2025-01-09 18:30:43\",\"John Doe\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\n" . - "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"3\",\"1\",\"Don't recommend \",\"Unusable after just a couple games. Expected better. Would not recommend.\",\"2025-01-09 19:56:37\",\"Tim Cook\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\n" . - "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"4\",\"4\",\"Overall satisfied\",\"Could have been better but overall satisfied with my purchase.\",\"2025-01-15 23:04:29\",\"Tom Manning\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\n"; + private string $csv_data = "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"2\",\"5\",\"Great product\",\"Very happy with this purchase. Would buy again.\",\"2025-01-09 18:30:43\",\"John Doe\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n" . + "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"3\",\"1\",\"Don't recommend \",\"Unusable after just a couple games. Expected better. Would not recommend.\",\"2025-01-09 19:56:37\",\"Tim Cook\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n" . + "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"4\",\"4\",\"Overall satisfied\",\"Could have been better but overall satisfied with my purchase.\",\"2025-01-15 23:04:29\",\"Tom Manning\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n"; /** * Constructor. diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index eb8c85923..0e93a0d2d 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -45,8 +45,8 @@ public function __construct() { $this->data_stream_name = FeedManager::EXAMPLE; // Using the headers for ratings and reviews for this proof of concept. $header = 'aggregator,store.name,store.id,store.store_urls,review_id,rating,title,content,created_at,' . - 'incentivized,has_verified_purchase,language,reviewer.name,reviewer.is_anonymous,product.name,product.url,' . - 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; + 'reviewer.name,reviewer.reviewerID,product.name,product.url,' . + 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $this->data_stream_name, $header ) ); $scheduler = new ActionScheduler(); @@ -62,11 +62,10 @@ public function __construct() { * @since 3.5.0 */ protected function add_hooks( string $heartbeat ) { - add_action( $heartbeat, array( $this, self::SCHEDULE_IMMEDIATE_CALL_BACK ) ); + add_action( $heartbeat, array( $this, self::SCHEDULE_CALL_BACK ) ); add_action( self::modify_action_name( self::GENERATE_FEED_ACTION ), array( $this, self::REGENERATE_CALL_BACK ) ); add_action( self::modify_action_name( self::FEED_GEN_COMPLETE_ACTION ), array( $this, self::UPLOAD_CALL_BACK ) ); add_action( self::LEGACY_API_PREFIX . self::modify_action_name( self::REQUEST_FEED_ACTION ), array( $this, self::STREAM_CALL_BACK ) ); - add_action( 'schedule_immediate_example_file', array( $this, 'schedule_feed_generation_immediately' ) ); } /** @@ -75,7 +74,7 @@ protected function add_hooks( string $heartbeat ) { * * @since 3.5.0 */ - public function schedule_feed_generation() { + public function schedule_feed_generation(): void { /** * Filter the interval for generating the example feed. * @@ -96,28 +95,12 @@ public function schedule_feed_generation() { } } - /** - * Allows an admin to schedule the feed generation immediately. - * - * @since 3.5.0 - */ - public function schedule_feed_generation_immediately() { - $schedule_action_hook_name = self::modify_action_name( self::GENERATE_FEED_ACTION ); - as_schedule_recurring_action( - time(), - 600, - $schedule_action_hook_name, - array(), - \WC_Facebookcommerce::instance()->get_id_dasherized() - ); - } - /** * Regenerates the example feed based on the defined schedule. * * @since 3.5.0 */ - public function regenerate_feed() { + public function regenerate_feed(): void { // Maybe use new ( experimental ), feed generation framework. if ( \WC_Facebookcommerce::instance()->get_integration()->is_new_style_feed_generation_enabled() ) { $this->feed_generator->queue_start(); @@ -133,7 +116,8 @@ public function regenerate_feed() { * * @since 3.5.0 */ - public function send_request_to_upload_feed() { + public function send_request_to_upload_feed(): void { + // For POC, replace URL with a remote hosted url that is running this code $data = array( 'url' => self::get_feed_data_url(), 'feed_type' => 'PRODUCT_RATINGS_AND_REVIEWS', @@ -160,7 +144,7 @@ public function send_request_to_upload_feed() { * @throws PluginException If file issue comes up. * @since 3.5.0 */ - public function handle_feed_data_request() { + public function handle_feed_data_request(): void { \WC_Facebookcommerce_Utils::log( 'ExampleFeed: Meta is requesting feed file.' ); $file_path = $this->feed_handler->get_feed_writer()->get_file_path(); @@ -223,11 +207,12 @@ public function handle_feed_data_request() { * @return string */ public function get_feed_data_url(): string { - $query_args = array( - 'wc-api' => self::modify_action_name( self::REQUEST_FEED_ACTION ), - 'secret' => self::get_feed_secret(), - ); - // phpcs:ignore + $query_args = array( + 'wc-api' => self::modify_action_name( self::REQUEST_FEED_ACTION ), + 'secret' => self::get_feed_secret(), + ); + + // phpcs:ignore // nosemgrep: audit.php.wp.security.xss.query-arg return add_query_arg( $query_args, home_url( '/' ) ); } @@ -240,15 +225,13 @@ public function get_feed_data_url(): string { * @return string */ public function get_feed_secret(): string { - /* //phpcs:ignore $secret = get_option( self::OPTION_FEED_URL_SECRET, '' ); if ( ! $secret ) { $secret = wp_hash( 'example-feed-' . time() ); update_option( self::OPTION_FEED_URL_SECRET, $secret ); } - return $secret;*/ - return 'secret'; + return $secret; } /** diff --git a/package-lock.json b/package-lock.json index 0031002f8..ce3736e31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "facebook-for-woocommerce", - "version": "3.3.0", + "version": "3.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "facebook-for-woocommerce", - "version": "3.3.0", + "version": "3.4.1", "license": "GPL-2.0", "devDependencies": { "@wordpress/env": "^9.10.0", @@ -4619,9 +4619,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001696", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", - "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "version": "1.0.30001701", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz", + "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==", "dev": true, "funding": [ { @@ -22321,9 +22321,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001696", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", - "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "version": "1.0.30001701", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz", + "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==", "dev": true }, "capture-exit": { From 31ccc8af8438c38ed0390688fe5944b1899d1463 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Sun, 2 Mar 2025 23:00:48 -0800 Subject: [PATCH 30/38] remove package-lock.json from changes --- package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce3736e31..0031002f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "facebook-for-woocommerce", - "version": "3.4.1", + "version": "3.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "facebook-for-woocommerce", - "version": "3.4.1", + "version": "3.3.0", "license": "GPL-2.0", "devDependencies": { "@wordpress/env": "^9.10.0", @@ -4619,9 +4619,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001701", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz", - "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==", + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", "dev": true, "funding": [ { @@ -22321,9 +22321,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001701", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz", - "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==", + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", "dev": true }, "capture-exit": { From 1c9d8d10d4d94b7940d74a79221ceb3cfdd6aa66 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Sun, 2 Mar 2025 23:23:44 -0800 Subject: [PATCH 31/38] changes to api with comments --- includes/API.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/includes/API.php b/includes/API.php index 60dc6915e..7a244bf15 100644 --- a/includes/API.php +++ b/includes/API.php @@ -105,7 +105,7 @@ protected function perform_request( $request ): API\Response { /** * Using this to perform a hard coded API request for proof of concept * Current use case: hitting GraphPartnerIntegrationFileUpdatePost with a Magento Store for POC - * Consider keeping: It will be useful to have the ability to hit the API from local without configuring new shop and getting permissions + * Consider keeping: For testing, good to have the ability to hit an API from local without configuring new shop and getting permissions for each dev * @param $request * @param $access_token * @return Response @@ -593,7 +593,8 @@ public function create_upload( string $product_feed_id, array $data ) { public function create_common_upload( string $cpi_id, array $data ): Response { $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); - return $this->perform_stub_request( $request,'EAACxonUmtyIBO9ovBryXe2LxuFC3QThLhmpWkSvIk2UoHS3WixeGESCd3HhzZAyKB3E0F6gvoJJxyKgLRSSQvcpDbbAV17E7vtZCBDp4Q9vFzVFyWMUFEZBvohBIaPbK9813ooqhAsuXOL2a27BgLd6G4FHrO75nPeEouGFJheMWJ3ZC9vBLyXfEEX6WOZB2F0yvjuKC6CNGMcqolDieZALkum' ); + // For POC testing, use perform_stub_request with auth token of shop that can hit the endpoint + return $this->perform_request( $request); } From 6573a4f0992edebfb2c4260bba95817db4b9283a Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Sun, 2 Mar 2025 23:55:36 -0800 Subject: [PATCH 32/38] add unit test --- tests/Unit/ApiTest.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/Unit/ApiTest.php b/tests/Unit/ApiTest.php index e19f6a91c..79da11a65 100644 --- a/tests/Unit/ApiTest.php +++ b/tests/Unit/ApiTest.php @@ -775,4 +775,35 @@ public function test_create_upload_request() { $response = $this->api->create_upload( $product_feed_id, $data ); $this->assertFalse( $response->has_api_error() ); } + + /** + * Tests create common upload feed request to Facebook. + * + * @return void + * @throws ApiException In case of network request error. + */ + public function test_create_common_upload_request() { + $cpi = '24316596247984028'; + + $data = array( + 'url' => 'http://example.com/?wc-api=wc_facebook_get_feed_data_example&secret=c4b8c3c46145aac6519e3f8a28bc86f2', + 'feed_type' => 'PRODUCT_RATINGS_AND_REVIEWS', + 'update_type' => 'CREATE', + ); + + $response = function( $result, $parsed_args, $url ) use ( $cpi ) { + $this->assertEquals( 'POST', $parsed_args['method'] ); + $this->assertEquals( "{$this->endpoint}{$this->version}/{$cpi}/file_update", $url ); + return [ + 'response' => [ + 'code' => 200, + 'message' => 'OK', + ], + ]; + }; + add_filter( 'pre_http_request', $response, 10, 3 ); + + $response = $this->api->create_common_upload( $cpi, $data ); + $this->assertFalse( $response->has_api_error() ); + } } From a555d2b4701de64e4353922bdd59ebca5b3cb063 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Mon, 3 Mar 2025 01:11:04 -0800 Subject: [PATCH 33/38] linter corrections; comment out feed manager instantiation --- class-wc-facebookcommerce.php | 1 + includes/Feed/CsvFeedFileWriter.php | 4 ++-- includes/Feed/ExampleFeed.php | 12 ++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/class-wc-facebookcommerce.php b/class-wc-facebookcommerce.php index b414a0d0e..cecf7ad30 100644 --- a/class-wc-facebookcommerce.php +++ b/class-wc-facebookcommerce.php @@ -191,6 +191,7 @@ public function init() { $this->heartbeat->init(); $this->feed_manager = new WooCommerce\Facebook\Feed\FeedManager(); + $this->checkout = new WooCommerce\Facebook\Checkout(); $this->product_feed = new WooCommerce\Facebook\Products\Feed(); $this->products_stock_handler = new WooCommerce\Facebook\Products\Stock(); $this->products_sync_handler = new WooCommerce\Facebook\Products\Sync(); diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index 429ba7f9c..fb6a5c0fe 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -52,8 +52,8 @@ class CsvFeedFileWriter implements FeedFileWriter { * @since 3.5.0 */ private string $csv_data = "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"2\",\"5\",\"Great product\",\"Very happy with this purchase. Would buy again.\",\"2025-01-09 18:30:43\",\"John Doe\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n" . - "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"3\",\"1\",\"Don't recommend \",\"Unusable after just a couple games. Expected better. Would not recommend.\",\"2025-01-09 19:56:37\",\"Tim Cook\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n" . - "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"4\",\"4\",\"Overall satisfied\",\"Could have been better but overall satisfied with my purchase.\",\"2025-01-15 23:04:29\",\"Tom Manning\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n"; + "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"3\",\"1\",\"Don't recommend \",\"Unusable after just a couple games. Expected better. Would not recommend.\",\"2025-01-09 19:56:37\",\"Tim Cook\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n" . + "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"4\",\"4\",\"Overall satisfied\",\"Could have been better but overall satisfied with my purchase.\",\"2025-01-15 23:04:29\",\"Tom Manning\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n"; /** * Constructor. diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 0e93a0d2d..a207dbb28 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -45,8 +45,8 @@ public function __construct() { $this->data_stream_name = FeedManager::EXAMPLE; // Using the headers for ratings and reviews for this proof of concept. $header = 'aggregator,store.name,store.id,store.store_urls,review_id,rating,title,content,created_at,' . - 'reviewer.name,reviewer.reviewerID,product.name,product.url,' . - 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; + 'reviewer.name,reviewer.reviewerID,product.name,product.url,' . + 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $this->data_stream_name, $header ) ); $scheduler = new ActionScheduler(); @@ -207,10 +207,10 @@ public function handle_feed_data_request(): void { * @return string */ public function get_feed_data_url(): string { - $query_args = array( - 'wc-api' => self::modify_action_name( self::REQUEST_FEED_ACTION ), - 'secret' => self::get_feed_secret(), - ); + $query_args = array( + 'wc-api' => self::modify_action_name( self::REQUEST_FEED_ACTION ), + 'secret' => self::get_feed_secret(), + ); // phpcs:ignore // nosemgrep: audit.php.wp.security.xss.query-arg From c9c64f5d96cd11a3151e3de19fb3e373d5665481 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Mon, 3 Mar 2025 02:07:55 -0800 Subject: [PATCH 34/38] Address comments --- includes/API.php | 4 ++-- includes/API/CommonFeedUploads/Create/Request.php | 4 +++- includes/Feed/ExampleFeed.php | 2 +- includes/Products/Feed.php | 2 +- tests/Unit/ApiTest.php | 4 ++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/includes/API.php b/includes/API.php index 7a244bf15..405033987 100644 --- a/includes/API.php +++ b/includes/API.php @@ -576,7 +576,7 @@ public function read_upload( string $product_feed_upload_id ) { * @throws ApiException * @throws API\Exceptions\Request_Limit_Reached */ - public function create_upload( string $product_feed_id, array $data ) { + public function create_product_feed_upload( string $product_feed_id, array $data ) { $request = new API\ProductCatalog\ProductFeedUploads\Create\Request( $product_feed_id, $data ); $this->set_response_handler( API\ProductCatalog\ProductFeedUploads\Create\Response::class ); return $this->perform_request( $request ); @@ -590,7 +590,7 @@ public function create_upload( string $product_feed_id, array $data ) { * @throws Request_Limit_Reached * @throws ApiException */ - public function create_common_upload( string $cpi_id, array $data ): Response { + public function create_common_data_feed_upload( string $cpi_id, array $data ): Response { $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); // For POC testing, use perform_stub_request with auth token of shop that can hit the endpoint diff --git a/includes/API/CommonFeedUploads/Create/Request.php b/includes/API/CommonFeedUploads/Create/Request.php index f8f8ada8c..32c4536c2 100644 --- a/includes/API/CommonFeedUploads/Create/Request.php +++ b/includes/API/CommonFeedUploads/Create/Request.php @@ -20,6 +20,7 @@ * Request object for the Common Feed Upload. */ class Request extends ApiRequest { + const CPI_ENDPOINT = 'file_update'; /** * Constructs the request. @@ -29,7 +30,8 @@ class Request extends ApiRequest { * @since 3.5.0 */ public function __construct( string $cpi_id, array $data ) { - parent::__construct( "/{$cpi_id}/file_update", 'POST' ); + $endpoint = self::CPI_ENDPOINT; + parent::__construct( "/{$cpi_id}/{$endpoint}", 'POST' ); parent::set_data( $data ); } } diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index a207dbb28..267d106e7 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -128,7 +128,7 @@ public function send_request_to_upload_feed(): void { $cpi_id = \WC_Facebookcommerce::instance()->get_integration()->get_commerce_partner_integration_id(); \WC_Facebookcommerce::instance() ->get_api() - ->create_common_upload( $cpi_id, $data ); + ->create_common_data_feed_upload( $cpi_id, $data ); } catch ( Exception $e ) { // Log the error and continue. \WC_Facebookcommerce::instance()->log( 'Failed to create example feed upload request: ' . $e->getMessage() ); diff --git a/includes/Products/Feed.php b/includes/Products/Feed.php index 75114dd79..2aef89fa8 100644 --- a/includes/Products/Feed.php +++ b/includes/Products/Feed.php @@ -204,7 +204,7 @@ public function send_request_to_upload_feed() { ]; try { - facebook_for_woocommerce()->get_api()->create_upload( $feed_id, $data ); + facebook_for_woocommerce()->get_api()->create_product_feed_upload( $feed_id, $data ); } catch ( Exception $exception ) { facebook_for_woocommerce()->log( 'Failed to create feed upload request: ' . $exception->getMessage() ); } diff --git a/tests/Unit/ApiTest.php b/tests/Unit/ApiTest.php index 79da11a65..4a19ea36f 100644 --- a/tests/Unit/ApiTest.php +++ b/tests/Unit/ApiTest.php @@ -772,7 +772,7 @@ public function test_create_upload_request() { }; add_filter( 'pre_http_request', $response, 10, 3 ); - $response = $this->api->create_upload( $product_feed_id, $data ); + $response = $this->api->create_product_feed_upload( $product_feed_id, $data ); $this->assertFalse( $response->has_api_error() ); } @@ -803,7 +803,7 @@ public function test_create_common_upload_request() { }; add_filter( 'pre_http_request', $response, 10, 3 ); - $response = $this->api->create_common_upload( $cpi, $data ); + $response = $this->api->create_common_data_feed_upload( $cpi, $data ); $this->assertFalse( $response->has_api_error() ); } } From 9c952b5fa17af59176293701bf83504ee7868f80 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Tue, 4 Mar 2025 17:22:46 -0800 Subject: [PATCH 35/38] Prototype; currently running on http://44.243.196.123/ --- class-wc-facebookcommerce.php | 2 +- includes/API.php | 2 +- includes/Feed/AbstractFeed.php | 181 ++++++++++++++++++++--- includes/Feed/CsvFeedFileWriter.php | 72 +++++----- includes/Feed/ExampleFeed.php | 190 +++---------------------- includes/Feed/ExampleFeedGenerator.php | 75 ++++++++-- includes/Feed/ExampleFeedHandler.php | 73 +++++++++- includes/Feed/FeedFileWriter.php | 6 +- includes/Feed/FeedGenerator.php | 1 - includes/Feed/FeedHandler.php | 7 + 10 files changed, 364 insertions(+), 245 deletions(-) diff --git a/class-wc-facebookcommerce.php b/class-wc-facebookcommerce.php index cecf7ad30..8b436ea64 100644 --- a/class-wc-facebookcommerce.php +++ b/class-wc-facebookcommerce.php @@ -190,7 +190,7 @@ public function init() { $this->heartbeat = new Heartbeat( WC()->queue() ); $this->heartbeat->init(); - $this->feed_manager = new WooCommerce\Facebook\Feed\FeedManager(); + $this->feed_manager = new WooCommerce\Facebook\Feed\FeedManager(); $this->checkout = new WooCommerce\Facebook\Checkout(); $this->product_feed = new WooCommerce\Facebook\Products\Feed(); $this->products_stock_handler = new WooCommerce\Facebook\Products\Stock(); diff --git a/includes/API.php b/includes/API.php index 405033987..2e6621729 100644 --- a/includes/API.php +++ b/includes/API.php @@ -594,7 +594,7 @@ public function create_common_data_feed_upload( string $cpi_id, array $data ): R $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); // For POC testing, use perform_stub_request with auth token of shop that can hit the endpoint - return $this->perform_request( $request); + return $this->perform_stub_request( $request, 'EAACxonUmtyIBO7oBL4WBGS5oINZCZA1iFnQ5sTcmpdhUZCfACNulQabk18dsBt2tnLvwHmWlEk0XX3uTWECNvxCFGsIcGZCZC32moYFgJZA8g6UNO0ZCjFRdISVZChBS1TSxXBGLwhpdE8SF8UxeW4oBTw51niWYxVC4xh89XhDgZCevZCokZBLz6DaqIZCdAZCdZABjvPFIeZB7yMlG1X7rt2nLoJvuWtq' ); } diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 6db26056c..8b3f9c122 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -10,6 +10,10 @@ namespace WooCommerce\Facebook\Feed; +use WooCommerce\Facebook\Framework\Api\Exception; +use WooCommerce\Facebook\Framework\Helper; +use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException; + defined( 'ABSPATH' ) || exit; /** @@ -39,6 +43,8 @@ abstract class AbstractFeed { const STREAM_CALL_BACK = 'handle_feed_data_request'; /** Hook prefix for Legacy REST API hook name */ const LEGACY_API_PREFIX = 'woocommerce_api_'; + /** @var string the WordPress option name where the secret included in the feed URL is stored */ + const OPTION_FEED_URL_SECRET = 'wc_facebook_feed_url_secret_'; /** @@ -58,57 +64,192 @@ abstract class AbstractFeed { protected FeedHandler $feed_handler; /** - * Schedules the recurring feed generation. + * The name of the data feed. * - * This method must be implemented by the concrete feed class, usually by providing a recurring interval + * @var string + */ + protected string $data_stream_name; + + /** + * The option name for the feed URL secret. * - * @since 3.5.0 + * @var string + */ + protected string $feed_url_secret_option_name; + + /** + * The type of feed as per the endpoint requirements. + * + * @var string + */ + protected string $feed_type; + + /** + * The interval in seconds for the feed generation. + * + * @var int */ - abstract public function schedule_feed_generation(); + protected int $gen_feed_interval; /** - * The method ensures that the feed is regenerated based on the defined schedule. + * Schedules the recurring feed generation. + * + * This method must be implemented by the concrete feed class, usually by providing a recurring interval * * @since 3.5.0 */ - abstract public function regenerate_feed(); + public function schedule_feed_generation(): void { + $schedule_action_hook_name = self::GENERATE_FEED_ACTION . $this->data_stream_name; + if ( ! as_next_scheduled_action( $schedule_action_hook_name ) ) { + as_schedule_recurring_action( + time(), + $this->gen_feed_interval, + $schedule_action_hook_name, + array(), + facebook_for_woocommerce()->get_id_dasherized() + ); + } + } /** - * Once feed regenerated, trigger upload via create_upload API and trigger the action for handling the upload + * Regenerates the example feed based on the defined schedule. * * @since 3.5.0 */ - abstract public function send_request_to_upload_feed(); + public function regenerate_feed(): void { + // Maybe use new ( experimental ), feed generation framework. + if ( \WC_Facebookcommerce::instance()->get_integration()->is_new_style_feed_generation_enabled() ) { + $this->feed_generator->queue_start(); + } else { + $this->feed_handler->generate_feed_file(); + } + } /** - * Handles the feed data request. + * Trigger the upload flow + * Once feed regenerated, trigger upload via create_upload API + * This will hit the url defined in the class and trigger the handle streaming file * * @since 3.5.0 */ - abstract public function handle_feed_data_request(); + public function send_request_to_upload_feed(): void { + $name = $this->data_stream_name; + $data = array( + 'url' => self::get_feed_data_url(), + 'feed_type' => $this->feed_type, + 'update_type' => 'CREATE', + ); + + try { + $cpi_id = \WC_Facebookcommerce::instance()->get_integration()->get_commerce_partner_integration_id(); + facebook_for_woocommerce()-> + get_api()-> + create_common_data_feed_upload( $cpi_id, $data ); + } catch ( Exception $e ) { + // Log the error and continue. + \WC_Facebookcommerce_Utils::log( "{$name} feed: Failed to create feed upload request: " . $e->getMessage() ); + } + } /** - * Gets the URL for retrieving the feed data. + * Gets the URL for retrieving the feed data using legacy WooCommerce REST API. + * Sample url: + * https://your-site-url.com/?wc-api=wc_facebook_get_feed_data_example&secret=your_generated_secret * - * @return string The URL for retrieving the feed data. * @since 3.5.0 + * @return string */ - abstract public function get_feed_data_url(): string; + public function get_feed_data_url(): string { + $query_args = array( + 'wc-api' => self::REQUEST_FEED_ACTION . $this->data_stream_name, + 'secret' => self::get_feed_secret(), + ); + + // phpcs:ignore + // nosemgrep: audit.php.wp.security.xss.query-arg + return add_query_arg( $query_args, home_url( '/' ) ); + } + /** - * Gets the secret value/ token that should be included in the feed URL. + * Gets the secret value that should be included in the legacy WooCommerce REST API URL. * - * @return string The secret value for the feed URL. * @since 3.5.0 + * @return string */ - abstract public function get_feed_secret(): string; + public function get_feed_secret(): string { +/* $secret = get_option( $this->feed_url_secret_option_name, '' ); + if ( ! $secret ) { + $secret = wp_hash( 'example-feed-' . time() ); + update_option( $this->feed_url_secret_option_name, $secret ); + } + + return $secret;*/ + return 'secret'; + } /** - * Modifies the action name by appending the data stream name. + * Callback function that streams the feed file to the GraphPartnerIntegrationFileUpdatePost + * Ex: https://your-site-url.com/?wc-api=wc_facebook_get_feed_data_example&secret=your_generated_secret + * The above WooC Legacy REST API will trigger the handle_feed_data_request method + * See LegacyRequestApiStub.php for more details * - * @param string $action_name The name of the hook. - * @return string The modified action name. + * @throws PluginException If file issue comes up. * @since 3.5.0 */ - abstract public static function modify_action_name( string $action_name ): string; + public function handle_feed_data_request(): void { + $name = $this->data_stream_name; + \WC_Facebookcommerce_Utils::log( "{$name} feed: Meta is requesting feed file." ); + + $file_path = $this->feed_handler->get_feed_writer()->get_file_path(); + + // regenerate if the file doesn't exist. + if ( ! file_exists( $file_path ) ) { + $this->feed_handler->generate_feed_file(); + } + + try { + // bail early if the feed secret is not included or is not valid. + if ( self::get_feed_secret() !== Helper::get_requested_value( 'secret' ) ) { + throw new PluginException( "{$name} feed: Invalid secret provided.", 401 ); + } + + // bail early if the file can't be read. + if ( ! is_readable( $file_path ) ) { + + throw new PluginException( "{$name}: File at path ' . $file_path . ' is not readable.", 404 ); + } + + // set the download headers. + header( 'Content-Type: text/csv; charset=utf-8' ); + header( 'Content-Description: File Transfer' ); + header( 'Content-Disposition: attachment; filename="' . basename( $file_path ) . '"' ); + header( 'Expires: 0' ); + header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' ); + header( 'Pragma: public' ); + header( 'Content-Length:' . filesize( $file_path ) ); + + // phpcs:ignore + $file = @fopen( $file_path, 'rb' ); + if ( ! $file ) { + throw new PluginException( "{$name} feed: Could not open feed file.", 500 ); + } + + // fpassthru might be disabled in some hosts (like Flywheel). + // phpcs:ignore + if ( \WC_Facebookcommerce_Utils::is_fpassthru_disabled() || ! @fpassthru( $file ) ) { + \WC_Facebookcommerce_Utils::log( "{$name} feed: fpassthru is disabled: getting file contents" ); + //phpcs:ignore + $contents = @stream_get_contents( $file ); + if ( ! $contents ) { + throw new PluginException( 'Could not get feed file contents.', 500 ); + } + echo $contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } catch ( \Exception $exception ) { + \WC_Facebookcommerce_Utils::log( "{$name} feed: Could not serve feed. " . $exception->getMessage() . ' (' . $exception->getCode() . ')' ); + status_header( $exception->getCode() ); + } + exit; + } } diff --git a/includes/Feed/CsvFeedFileWriter.php b/includes/Feed/CsvFeedFileWriter.php index fb6a5c0fe..d1380d211 100644 --- a/includes/Feed/CsvFeedFileWriter.php +++ b/includes/Feed/CsvFeedFileWriter.php @@ -45,16 +45,6 @@ class CsvFeedFileWriter implements FeedFileWriter { */ private string $header_row; - /** - * Data to write to feed file - * - * @var string - * @since 3.5.0 - */ - private string $csv_data = "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"2\",\"5\",\"Great product\",\"Very happy with this purchase. Would buy again.\",\"2025-01-09 18:30:43\",\"John Doe\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n" . - "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"3\",\"1\",\"Don't recommend \",\"Unusable after just a couple games. Expected better. Would not recommend.\",\"2025-01-09 19:56:37\",\"Tim Cook\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n" . - "\"magento\",\"Default Store View\",\"1413745412827209\",\"['http://35.91.150.25/']\",\"4\",\"4\",\"Overall satisfied\",\"Could have been better but overall satisfied with my purchase.\",\"2025-01-15 23:04:29\",\"Tom Manning\",\"\",\"Baseball\",\"http://35.91.150.25/catalog/product/view/id/12/s/baseball/\",\"['/b/a/baseball.jpg']\",\"['Baseball']\",\"US\"\n"; - /** * Constructor. * @@ -70,10 +60,11 @@ public function __construct( string $feed_name, string $header_row ) { /** * Write the feed file. * + * @param array $data The data to write to the feed file. * @return void * @since 3.5.0 */ - public function write_feed_file() { + public function write_feed_file( array $data ): void { try { $this->create_feed_directory(); $this->create_files_to_protect_feed_directory(); @@ -81,8 +72,8 @@ public function write_feed_file() { // Step 1: Prepare the temporary empty feed file with header row. $temp_feed_file = $this->prepare_temporary_feed_file(); - // Step 2: Write products feed into the temporary feed file. - $this->write_temp_feed_file(); + // Step 2: Write feed into the temporary feed file. + $this->write_temp_feed_file( $data ); // Step 3: Rename temporary feed file to final feed file. $this->promote_temp_file(); @@ -101,33 +92,44 @@ public function write_feed_file() { } /** - * Generates the product catalog feed file. + * Generates the feed file. * * @throws PluginException If the directory could not be created. * @since 3.5.0 */ - public function create_feed_directory() { - $directory_created = wp_mkdir_p( $this->get_file_directory() ); + 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 product catalog feed directory', 'facebook-for-woocommerce' ), 500 ); + throw new PluginException( __( "Could not create feed directory at {$file_directory}", 'facebook-for-woocommerce' ), 500 ); } } /** * Write the feed data to the temporary feed file. * + * @param array $data The data to write to the feed file. * @since 3.5.0 */ - public function write_temp_feed_file() { - /** - * Real implementation would get the necessary objects and turn them into a row to write to feed file. - * For POC, will just write to temp file. - */ - //phpcs:ignore -- current product feed does not use Wordpress file i/o functions + public function write_temp_feed_file( array $data ): void { + //phpcs:ignore -- use php file i/o functions $temp_feed_file = fopen( $this->get_temp_file_path(), 'a' ); if ( ! empty( $this->get_temp_file_path() ) ) { - fwrite( $temp_feed_file, $this->csv_data ); //phpcs:ignore + // Turn headers into an array of string accessors + $accessors = str_getcsv( $this->header_row ); + // For each object, turn into a csv row via accessors + foreach ( $data as $obj ) { + $line = ''; + foreach ( $accessors as $accessor ) { + $val = $obj[ $accessor ]; + $line .= "{$val},"; + } + + $line = rtrim( $line, ',' ); + $line .= PHP_EOL; + fwrite( $temp_feed_file, $line ); //phpcs:ignore + } } if ( ! empty( $temp_feed_file ) ) { @@ -140,7 +142,7 @@ public function write_temp_feed_file() { * * @since 3.5.0 */ - public function create_files_to_protect_feed_directory() { + public function create_files_to_protect_feed_directory(): void { $feed_directory = trailingslashit( $this->get_file_directory() ); $files = array( @@ -159,7 +161,7 @@ public function create_files_to_protect_feed_directory() { foreach ( $files as $file ) { if ( wp_mkdir_p( $file['base'] ) && ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) ) { - // phpcs:ignore -- current product feed does not use Wordpress file i/o functions + // phpcs:ignore -- use php file i/o functions $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'w' ); if ( $file_handle ) { fwrite( $file_handle, $file['content'] ); //phpcs:ignore @@ -210,7 +212,7 @@ public function get_file_directory(): string { */ public function get_file_name(): string { $feed_secret = facebook_for_woocommerce()->feed_manager->get_feed_secret( $this->feed_name ); - return sprintf( self::FILE_NAME, $this->feed_name, wp_hash( $feed_secret ) ); + return sprintf( self::FILE_NAME, $this->feed_name, $feed_secret ); } /** @@ -233,26 +235,26 @@ public function get_temp_file_name(): string { */ public function prepare_temporary_feed_file() { $temp_file_path = $this->get_temp_file_path(); - //phpcs:ignore -- current product feed does not use Wordpress file i/o functions + //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 feed file for writing.', 'facebook-for-woocommerce' ), 500 ); + throw new PluginException( __( "Could not open file {$temp_file_path} for writing.", 'facebook-for-woocommerce' ), 500 ); } $file_path = $this->get_file_path(); // Check if we will be able to write to the final feed file. - //phpcs:ignore -- current product feed does not use Wordpress file i/o functions + //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 the product catalog feed file for writing', 'facebook-for-woocommerce' ), 500 ); + throw new PluginException( __( "Could not open file {$file_path} for writing.", 'facebook-for-woocommerce' ), 500 ); } - //phpcs:ignore -- current product feed does not use Wordpress file i/o functions + //phpcs:ignore -- use php file i/o functions fwrite( $temp_feed_file, $this->header_row); return $temp_feed_file; } @@ -264,17 +266,17 @@ public function prepare_temporary_feed_file() { * @since 3.5.0 * @throws PluginException If the temporary feed file could not be renamed. */ - public function promote_temp_file() { + 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 -- current product feed does not use Wordpress file i/o functions + // 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 rename the product catalog feed file', 'facebook-for-woocommerce' ), 500 ); + throw new PluginException( __( "Could not promote temp file: {$temp_file_path}", 'facebook-for-woocommerce' ), 500 ); } } } diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 267d106e7..0fb8b116b 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -26,15 +26,10 @@ * @since 3.5.0 */ class ExampleFeed extends AbstractFeed { - - const EXAMPLE_FEED_INTERVAL = 'wc_facebook_example_feed_generation_interval'; - const OPTION_FEED_URL_SECRET = 'wc_facebook_example_feed_url_secret'; - - /** Name of datafeed for modifying action names. - * - * @var string - */ - private string $data_stream_name; + /** Header for the example feed file. @var string */ + const EXAMPLE_FEED_HEADER = 'aggregator,store.name,store.id,store.store_urls,review_id,rating,title,content,created_at,' . + 'reviewer.name,reviewer.reviewerID,product.name,product.url,' . + 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; /** * Constructor. @@ -42,13 +37,12 @@ class ExampleFeed extends AbstractFeed { * @since 3.5.0 */ public function __construct() { - $this->data_stream_name = FeedManager::EXAMPLE; - // Using the headers for ratings and reviews for this proof of concept. - $header = 'aggregator,store.name,store.id,store.store_urls,review_id,rating,title,content,created_at,' . - 'reviewer.name,reviewer.reviewerID,product.name,product.url,' . - 'product.image_urls,product.product_identifiers.skus,country' . PHP_EOL; + $this->data_stream_name = FeedManager::EXAMPLE; + $this->gen_feed_interval = DAY_IN_SECONDS; + $this->feed_type = 'PRODUCT_RATINGS_AND_REVIEWS'; + $this->feed_url_secret_option_name = self::OPTION_FEED_URL_SECRET . $this->data_stream_name; - $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $this->data_stream_name, $header ) ); + $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $this->data_stream_name, self::EXAMPLE_FEED_HEADER ) ); $scheduler = new ActionScheduler(); $this->feed_generator = new ExampleFeedGenerator( $scheduler, $this->feed_handler ); $this->feed_generator->init(); @@ -61,52 +55,11 @@ public function __construct() { * @param string $heartbeat The heartbeat interval for the feed generation. * @since 3.5.0 */ - protected function add_hooks( string $heartbeat ) { + protected function add_hooks( string $heartbeat ): void { add_action( $heartbeat, array( $this, self::SCHEDULE_CALL_BACK ) ); - add_action( self::modify_action_name( self::GENERATE_FEED_ACTION ), array( $this, self::REGENERATE_CALL_BACK ) ); - add_action( self::modify_action_name( self::FEED_GEN_COMPLETE_ACTION ), array( $this, self::UPLOAD_CALL_BACK ) ); - add_action( self::LEGACY_API_PREFIX . self::modify_action_name( self::REQUEST_FEED_ACTION ), array( $this, self::STREAM_CALL_BACK ) ); - } - - /** - * Schedules the recurring feed generation. - * This method must be implemented by the concrete feed class, usually by providing a recurring interval - * - * @since 3.5.0 - */ - public function schedule_feed_generation(): void { - /** - * Filter the interval for generating the example feed. - * - * @param int $interval The interval in seconds. - * @since 3.5.0 - */ - $interval = apply_filters( self::EXAMPLE_FEED_INTERVAL, DAY_IN_SECONDS ); - - $schedule_action_hook_name = self::modify_action_name( self::GENERATE_FEED_ACTION ); - if ( ! as_next_scheduled_action( $schedule_action_hook_name ) ) { - as_schedule_recurring_action( - time(), - max( 2, $interval ), - $schedule_action_hook_name, - array(), - \WC_Facebookcommerce::instance()->get_id_dasherized() - ); - } - } - - /** - * Regenerates the example feed based on the defined schedule. - * - * @since 3.5.0 - */ - public function regenerate_feed(): void { - // Maybe use new ( experimental ), feed generation framework. - if ( \WC_Facebookcommerce::instance()->get_integration()->is_new_style_feed_generation_enabled() ) { - $this->feed_generator->queue_start(); - } else { - $this->feed_handler->generate_feed_file(); - } + add_action( self::GENERATE_FEED_ACTION . $this->data_stream_name, array( $this, self::REGENERATE_CALL_BACK ) ); + add_action( self::FEED_GEN_COMPLETE_ACTION . $this->data_stream_name, array( $this, self::UPLOAD_CALL_BACK ) ); + add_action( self::LEGACY_API_PREFIX . self::REQUEST_FEED_ACTION . $this->data_stream_name, array( $this, self::STREAM_CALL_BACK ) ); } /** @@ -117,107 +70,24 @@ public function regenerate_feed(): void { * @since 3.5.0 */ public function send_request_to_upload_feed(): void { - // For POC, replace URL with a remote hosted url that is running this code + $name = $this->data_stream_name; $data = array( - 'url' => self::get_feed_data_url(), - 'feed_type' => 'PRODUCT_RATINGS_AND_REVIEWS', + 'url' => 'http://44.243.196.123/?wc-api=wc_facebook_get_feed_data_example&secret=secret', + 'feed_type' => $this->feed_type, 'update_type' => 'CREATE', ); try { $cpi_id = \WC_Facebookcommerce::instance()->get_integration()->get_commerce_partner_integration_id(); - \WC_Facebookcommerce::instance() - ->get_api() - ->create_common_data_feed_upload( $cpi_id, $data ); + facebook_for_woocommerce()-> + get_api()-> + create_common_data_feed_upload( $cpi_id, $data ); } catch ( Exception $e ) { // Log the error and continue. - \WC_Facebookcommerce::instance()->log( 'Failed to create example feed upload request: ' . $e->getMessage() ); + \WC_Facebookcommerce_Utils::log( "{$name} feed: Failed to create feed upload request: " . $e->getMessage() ); } } - /** - * Callback function that streams the feed file to the GraphPartnerIntegrationFileUpdatePost - * Ex: https://your-site-url.com/?wc-api=wc_facebook_get_feed_data_example&secret=your_generated_secret - * The above WooC Legacy REST API will trigger the handle_feed_data_request method - * See LegacyRequestApiStub.php for more details - * - * @throws PluginException If file issue comes up. - * @since 3.5.0 - */ - public function handle_feed_data_request(): void { - \WC_Facebookcommerce_Utils::log( 'ExampleFeed: Meta is requesting feed file.' ); - - $file_path = $this->feed_handler->get_feed_writer()->get_file_path(); - - // regenerate if the file doesn't exist. - if ( ! file_exists( $file_path ) ) { - $this->feed_handler->generate_feed_file(); - } - - try { - // bail early if the feed secret is not included or is not valid. - if ( self::get_feed_secret() !== Helper::get_requested_value( 'secret' ) ) { - throw new PluginException( 'ExampleFeed: Invalid secret provided.', 401 ); - } - - // bail early if the file can't be read. - if ( ! is_readable( $file_path ) ) { - throw new PluginException( 'ExampleFeed: File at path ' . $file_path . ' is not readable.', 404 ); - } - - // set the download headers. - header( 'Content-Type: text/csv; charset=utf-8' ); - header( 'Content-Description: File Transfer' ); - header( 'Content-Disposition: attachment; filename="' . basename( $file_path ) . '"' ); - header( 'Expires: 0' ); - header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' ); - header( 'Pragma: public' ); - header( 'Content-Length:' . filesize( $file_path ) ); - - // phpcs:ignore - $file = @fopen( $file_path, 'rb' ); - if ( ! $file ) { - throw new PluginException( 'ExampleFeed: Could not open feed file.', 500 ); - } - - // fpassthru might be disabled in some hosts (like Flywheel). - // phpcs:ignore - if ( \WC_Facebookcommerce_Utils::is_fpassthru_disabled() || ! @fpassthru( $file ) ) { - \WC_Facebookcommerce_Utils::log( 'ExampleFeed: fpassthru is disabled: getting file contents' ); - //phpcs:ignore - $contents = @stream_get_contents( $file ); - if ( ! $contents ) { - throw new PluginException( 'Could not get feed file contents.', 500 ); - } - echo $contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - } catch ( \Exception $exception ) { - \WC_Facebookcommerce_Utils::log( 'ExampleFeed: Could not serve feed. ' . $exception->getMessage() . ' (' . $exception->getCode() . ')' ); - status_header( $exception->getCode() ); - } - exit; - } - - /** - * Gets the URL for retrieving the product feed data using legacy WooCommerce REST API. - * Sample url: - * https://your-site-url.com/?wc-api=wc_facebook_get_feed_data_example&secret=your_generated_secret - * - * @since 3.5.0 - * @return string - */ - public function get_feed_data_url(): string { - $query_args = array( - 'wc-api' => self::modify_action_name( self::REQUEST_FEED_ACTION ), - 'secret' => self::get_feed_secret(), - ); - - // phpcs:ignore - // nosemgrep: audit.php.wp.security.xss.query-arg - return add_query_arg( $query_args, home_url( '/' ) ); - } - - /** * Gets the secret value that should be included in the legacy WooCommerce REST API URL. * @@ -225,24 +95,6 @@ public function get_feed_data_url(): string { * @return string */ public function get_feed_secret(): string { - $secret = get_option( self::OPTION_FEED_URL_SECRET, '' ); - if ( ! $secret ) { - $secret = wp_hash( 'example-feed-' . time() ); - update_option( self::OPTION_FEED_URL_SECRET, $secret ); - } - - return $secret; - } - - /** - * Modifies the action name by appending the data stream name. - * - * @param string $action_name The name of the hook. - * @return string The modified action name. - * @since 3.5.0 - */ - public static function modify_action_name( string $action_name ): string { - $name = FeedManager::EXAMPLE; - return "{$action_name}{$name}"; + return 'secret'; } } diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php index 62765b73a..cd3bd6670 100644 --- a/includes/Feed/ExampleFeedGenerator.php +++ b/includes/Feed/ExampleFeedGenerator.php @@ -59,7 +59,7 @@ protected function handle_end() { * * @since 3.5.0 */ - do_action( ExampleFeed::modify_action_name( AbstractFeed::FEED_GEN_COMPLETE_ACTION ) ); + do_action( AbstractFeed::FEED_GEN_COMPLETE_ACTION . FeedManager::EXAMPLE ); } /** @@ -95,7 +95,64 @@ protected function get_items_for_batch( int $batch_number, array $args ): array // For proof of concept, we will just return the review id for batch 1 // In parent classes, batch number starts with 1. if ( 1 === $batch_number ) { - return array( 2, 3, 4 ); + $obj_1 = [ + 'aggregator' => 'magento', + 'store.name' => 'Default Store View', + 'store.id' => '1413745412827209', + 'store.store_urls' => "['http://35.91.150.25/']", + 'review_id' => '2', + 'rating' => '5', + 'title' => 'Great product', + 'content' => 'Very happy with this purchase. Would buy again.', + 'created_at' => '2025-01-09 18:30:43', + 'reviewer.name' => 'John Doe', + 'reviewer.reviewerID' => '1', + 'product.name' => 'Baseball', + 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', + 'product.image_urls' => "['/b/a/baseball.jpg']", + 'product.product_identifiers.skus' => "['Baseball']", + 'country' => 'US', + ]; + + $obj_2 = [ + 'aggregator' => 'magento', + 'store.name' => 'Default Store View', + 'store.id' => '1413745412827209', + 'store.store_urls' => "['http://35.91.150.25/']", + 'review_id' => '3', + 'rating' => '1', + 'title' => "Don't recommend", + 'content' => 'Unusable after just a couple games. Expected better. Would not recommend.', + 'created_at' => '2025-01-09 19:56:37', + 'reviewer.name' => 'Tim Cook', + 'reviewer.reviewerID' => '2', + 'product.name' => 'Baseball', + 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', + 'product.image_urls' => "['/b/a/baseball.jpg']", + 'product.product_identifiers.skus' => "['Baseball']", + 'country' => 'US', + ]; + + $obj_3 = [ + 'aggregator' => 'magento', + 'store.name' => 'Default Store View', + 'store.id' => '1413745412827209', + 'store.store_urls' => "['http://35.91.150.25/']", + 'review_id' => '4', + 'rating' => '4', + 'title' => 'Overall satisfied', + 'content' => 'Could have been better but overall satisfied with my purchase.', + 'created_at' => '2025-01-15 23:04:29', + 'reviewer.name' => 'Tom Manning', + 'reviewer.reviewerID' => '3', + 'product.name' => 'Baseball', + 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', + 'product.image_urls' => "['/b/a/baseball.jpg']", + 'product.product_identifiers.skus' => "['Baseball']", + 'country' => 'US', + ]; + + return array( $obj_1, $obj_2, $obj_3 ); } else { return array(); } @@ -110,16 +167,7 @@ protected function get_items_for_batch( int $batch_number, array $args ): array * @since 3.5.0 */ protected function process_items( array $items, array $args ) { - // phpcs:ignore -- Using fopen to match existing implementation. - $temp_feed_file = fopen( $this->feed_writer->get_temp_file_path(), 'a' ); - // True override of write_feed_file would probably take an array of item ids or item objects - // For poc, will just write to the temp feed file. - $this->feed_writer->write_temp_feed_file(); - - if ( is_resource( $temp_feed_file ) ) { - //phpcs:ignore -- Using fclose to match existing implementation. - fclose( $temp_feed_file ); - } + $this->feed_writer->write_temp_feed_file( $items ); } /** @@ -132,7 +180,8 @@ protected function process_items( array $items, array $args ) { */ protected function process_item( $item, array $args ) { // Needed to satisfy the class inheritance - // Because of the i/o opening and closing original feed implementation foregoes this method. + // Because of the i/o opening and closing original feed implementation foregoes this method; + // It is more efficient to write each batch out and per object processing is done in write_feed_file(). } /** diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php index 4d0a80b9d..3830e7134 100644 --- a/includes/Feed/ExampleFeedHandler.php +++ b/includes/Feed/ExampleFeedHandler.php @@ -46,14 +46,14 @@ public function __construct( FeedFileWriter $feed_writer ) { * * @since 3.5.0 */ - public function generate_feed_file() { - $this->feed_writer->write_feed_file(); + public function generate_feed_file(): void { + $this->feed_writer->write_feed_file( $this->get_feed_data() ); /** * Trigger upload from ExampleFeed instance * * @since 3.5.0 */ - do_action( ExampleFeed::modify_action_name( AbstractFeed::FEED_GEN_COMPLETE_ACTION ) ); + do_action( AbstractFeed::FEED_GEN_COMPLETE_ACTION .FeedManager::EXAMPLE ); } /** @@ -65,4 +65,71 @@ public function generate_feed_file() { public function get_feed_writer(): FeedFileWriter { return $this->feed_writer; } + + /** + * Get the feed data and return as array of objects. + * + * @return array + * @since 3.5.0 + */ + public function get_feed_data(): array { + $obj_1 = [ + 'aggregator' => 'magento', + 'store.name' => 'Default Store View', + 'store.id' => '1413745412827209', + 'store.store_urls' => "['http://35.91.150.25/']", + 'review_id' => '2', + 'rating' => '5', + 'title' => 'Great product', + 'content' => 'Very happy with this purchase. Would buy again.', + 'created_at' => '2025-01-09 18:30:43', + 'reviewer.name' => 'John Doe', + 'reviewer.reviewerID' => '1', + 'product.name' => 'Baseball', + 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', + 'product.image_urls' => "['/b/a/baseball.jpg']", + 'product.product_identifiers.skus' => "['Baseball']", + 'country' => 'US', + ]; + + $obj_2 = [ + 'aggregator' => 'magento', + 'store.name' => 'Default Store View', + 'store.id' => '1413745412827209', + 'store.store_urls' => "['http://35.91.150.25/']", + 'review_id' => '3', + 'rating' => '1', + 'title' => "Don't recommend", + 'content' => 'Unusable after just a couple games. Expected better. Would not recommend.', + 'created_at' => '2025-01-09 19:56:37', + 'reviewer.name' => 'Tim Cook', + 'reviewer.reviewerID' => '2', + 'product.name' => 'Baseball', + 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', + 'product.image_urls' => "['/b/a/baseball.jpg']", + 'product.product_identifiers.skus' => "['Baseball']", + 'country' => 'US', + ]; + + $obj_3 = [ + 'aggregator' => 'magento', + 'store.name' => 'Default Store View', + 'store.id' => '1413745412827209', + 'store.store_urls' => "['http://35.91.150.25/']", + 'review_id' => '4', + 'rating' => '4', + 'title' => 'Overall satisfied', + 'content' => 'Could have been better but overall satisfied with my purchase.', + 'created_at' => '2025-01-15 23:04:29', + 'reviewer.name' => 'Tom Manning', + 'reviewer.reviewerID' => '3', + 'product.name' => 'Baseball', + 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', + 'product.image_urls' => "['/b/a/baseball.jpg']", + 'product.product_identifiers.skus' => "['Baseball']", + 'country' => 'US', + ]; + + return array( $obj_1, $obj_2, $obj_3 ); + } } diff --git a/includes/Feed/FeedFileWriter.php b/includes/Feed/FeedFileWriter.php index cffd1f834..adbb2282e 100644 --- a/includes/Feed/FeedFileWriter.php +++ b/includes/Feed/FeedFileWriter.php @@ -16,9 +16,10 @@ interface FeedFileWriter { /** * Write the feed file. * + * @param array $data The data to write to the feed file. * @since 3.5.0 */ - public function write_feed_file(); + public function write_feed_file( array $data ); /** * Create feed directory. @@ -94,7 +95,8 @@ public function promote_temp_file(); /** * Write to the temp feed file. * + * @param array $data The data to write to the feed file. * @since 3.5.0 */ - public function write_temp_feed_file(); + public function write_temp_feed_file( array $data ); } diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index d10faeb9a..f613773e6 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -104,7 +104,6 @@ protected function process_item( $item, array $args ) { /** * Get the name/slug of the job. - * Ex. generate_product_feed * * @return string * @since 3.5.0 diff --git a/includes/Feed/FeedHandler.php b/includes/Feed/FeedHandler.php index 787e96d41..588a8519d 100644 --- a/includes/Feed/FeedHandler.php +++ b/includes/Feed/FeedHandler.php @@ -26,4 +26,11 @@ public function generate_feed_file(); * @return FeedFileWriter */ public function get_feed_writer(): FeedFileWriter; + + /** + * Get the feed data and return as array of objects. + * + * @return array + */ + public function get_feed_data(): array; } From a3a11098eaf8d467d6453c9e2154e8ad201b9e8f Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Wed, 5 Mar 2025 13:36:24 -0800 Subject: [PATCH 36/38] Move most code from ExampleFeedGenerator to FeedGenerator --- includes/API.php | 2 +- includes/Feed/ExampleFeed.php | 2 +- includes/Feed/ExampleFeedGenerator.php | 94 -------------------------- includes/Feed/FeedGenerator.php | 57 +++++++++++----- 4 files changed, 42 insertions(+), 113 deletions(-) diff --git a/includes/API.php b/includes/API.php index 2e6621729..bb136cdd6 100644 --- a/includes/API.php +++ b/includes/API.php @@ -594,7 +594,7 @@ public function create_common_data_feed_upload( string $cpi_id, array $data ): R $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); // For POC testing, use perform_stub_request with auth token of shop that can hit the endpoint - return $this->perform_stub_request( $request, 'EAACxonUmtyIBO7oBL4WBGS5oINZCZA1iFnQ5sTcmpdhUZCfACNulQabk18dsBt2tnLvwHmWlEk0XX3uTWECNvxCFGsIcGZCZC32moYFgJZA8g6UNO0ZCjFRdISVZChBS1TSxXBGLwhpdE8SF8UxeW4oBTw51niWYxVC4xh89XhDgZCevZCokZBLz6DaqIZCdAZCdZABjvPFIeZB7yMlG1X7rt2nLoJvuWtq' ); + return $this->perform_stub_request( $request, 'EAACxonUmtyIBO4FIt1wXKF511sXHIbsWeWMUZB2qyaaQksZBqTvp44qpyEYjwrXJGSaZC0aizYyCKlFmbbOlFsbbpZBgybXm4gNVNzGfyx1ZARsiOprMrQe3GB9euDXl1LqBXZA5ZCA7TZCHVGDRQeAlTuWMZCgfCk3MwfniG0c85fW12LOXUCRZBGHkgwPpy2ona5R8Bv89OMKjMEV9oABfXiNMVOKwZDZD' ); } diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php index 0fb8b116b..62a61dfc8 100644 --- a/includes/Feed/ExampleFeed.php +++ b/includes/Feed/ExampleFeed.php @@ -44,7 +44,7 @@ public function __construct() { $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $this->data_stream_name, self::EXAMPLE_FEED_HEADER ) ); $scheduler = new ActionScheduler(); - $this->feed_generator = new ExampleFeedGenerator( $scheduler, $this->feed_handler ); + $this->feed_generator = new ExampleFeedGenerator( $scheduler, $this->feed_handler->get_feed_writer(), $this->data_stream_name ); $this->feed_generator->init(); $this->add_hooks( Heartbeat::HOURLY ); } diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php index cd3bd6670..d0358dbac 100644 --- a/includes/Feed/ExampleFeedGenerator.php +++ b/includes/Feed/ExampleFeedGenerator.php @@ -15,53 +15,6 @@ * @since 3.5.0 */ class ExampleFeedGenerator extends FeedGenerator { - /** - * Used to interact with the directory system. - * - * @var FeedFileWriter $feed_writer - */ - private FeedFileWriter $feed_writer; - - /** - * Constructor for this instance. - * - * @param ActionSchedulerInterface $action_scheduler Global scheduler. - * @param FeedHandler $feed_handler The feed handler instance for this feed. - */ - public function __construct( ActionSchedulerInterface $action_scheduler, FeedHandler $feed_handler ) { - parent::__construct( $action_scheduler, $feed_handler ); - $this->feed_writer = $feed_handler->get_feed_writer(); - } - - /** - * Handles the start of the feed generation process. - * - * @inheritdoc - * @since 3.5.0 - */ - protected function handle_start() { - // Create directory if not available and then the files to protect the directory. - $this->feed_writer->create_files_to_protect_feed_directory(); - $this->feed_writer->prepare_temporary_feed_file(); - } - - /** - * Handles the end of the feed generation process. - * - * @inheritdoc - * @since 3.5.0 - */ - protected function handle_end() { - $this->feed_writer->promote_temp_file(); - - /** - * Trigger upload from ExampleFeed instance - * - * @since 3.5.0 - */ - do_action( AbstractFeed::FEED_GEN_COMPLETE_ACTION . FeedManager::EXAMPLE ); - } - /** * Retrieves items for a specific batch. * @@ -157,51 +110,4 @@ protected function get_items_for_batch( int $batch_number, array $args ): array return array(); } } - - /** - * Processes a batch of items. - * - * @param array $items The items to process. - * @param array $args Additional arguments. - * @inheritdoc - * @since 3.5.0 - */ - protected function process_items( array $items, array $args ) { - $this->feed_writer->write_temp_feed_file( $items ); - } - - /** - * Processes a single item. - * - * @param mixed $item The item to process. - * @param array $args Additional arguments. - * @inheritdoc - * @since 3.5.0 - */ - protected function process_item( $item, array $args ) { - // Needed to satisfy the class inheritance - // Because of the i/o opening and closing original feed implementation foregoes this method; - // It is more efficient to write each batch out and per object processing is done in write_feed_file(). - } - - /** - * Gets the name of the feed generator. - * - * @return string The name of the feed generator. - * @inheritdoc - * @since 3.5.0 - */ - public function get_name(): string { - return self::class; - } - - /** - * Gets the batch size for the feed generation process. - * - * @return int The batch size. - * @inheritdoc - */ - protected function get_batch_size(): int { - return 1; - } } diff --git a/includes/Feed/FeedGenerator.php b/includes/Feed/FeedGenerator.php index f613773e6..8353595ec 100644 --- a/includes/Feed/FeedGenerator.php +++ b/includes/Feed/FeedGenerator.php @@ -28,41 +28,62 @@ */ class FeedGenerator extends AbstractChainedJob { /** - * The feed handler instance for the given feed. + * The feed writer instance for the given feed. * - * @var FeedHandler + * @var FeedFileWriter * @since 3.5.0 */ - protected FeedHandler $feed_handler; + protected FeedFileWriter $feed_writer; + + /** + * The name of the data feed. + * + * @var string + * @since 3.5.0 + */ + protected string $feed_name; /** * FeedGenerator constructor. * * @param ActionSchedulerInterface $action_scheduler The action scheduler instance. - * @param FeedHandler $feed_handler The feed handler instance. + * @param FeedFileWriter $feed_writer The feed handler instance. + * @param string $feed_name The name of the feed. * @since 3.5.0 */ - public function __construct( ActionSchedulerInterface $action_scheduler, FeedHandler $feed_handler ) { + public function __construct( ActionSchedulerInterface $action_scheduler, FeedFileWriter $feed_writer, string $feed_name ) { parent::__construct( $action_scheduler ); - $this->feed_handler = $feed_handler; + $this->feed_writer = $feed_writer; + $this->feed_name = $feed_name; } /** - * Called before starting the job. - * Override for specific data stream. + * Handles the start of the feed generation process. * + * @inheritdoc * @since 3.5.0 */ - protected function handle_start() { + protected function handle_start(): void { + // Create directory if not available and then the files to protect the directory. + $this->feed_writer->create_files_to_protect_feed_directory(); + $this->feed_writer->prepare_temporary_feed_file(); } /** - * Called after the finishing the job. - * Override for specific data stream. + * Handles the end of the feed generation process. * + * @inheritdoc * @since 3.5.0 */ - protected function handle_end() { + protected function handle_end(): void { + $this->feed_writer->promote_temp_file(); + + /** + * Trigger upload from ExampleFeed instance + * + * @since 3.5.0 + */ + do_action( AbstractFeed::FEED_GEN_COMPLETE_ACTION . $this->feed_name ); } /** @@ -84,11 +105,13 @@ protected function get_items_for_batch( int $batch_number, array $args ): array /** * Processes a batch of items. * - * @param array $items The items of the current batch, probably compiled as an object. - * @param array $args The args for the job. + * @param array $items The items to process. + * @param array $args Additional arguments. + * @inheritdoc * @since 3.5.0 */ - protected function process_items( array $items, array $args ) { + protected function process_items( array $items, array $args ): void { + $this->feed_writer->write_temp_feed_file( $items ); } /** @@ -109,7 +132,7 @@ protected function process_item( $item, array $args ) { * @since 3.5.0 */ public function get_name(): string { - return ''; + return $this->feed_name . '_feed_generator'; } /** @@ -129,6 +152,6 @@ public function get_plugin_name(): string { * @since 3.5.0 */ protected function get_batch_size(): int { - return -1; + return 1; } } From 5c1ba5f855db32a4be8f52ef39f387e9133bb456 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Wed, 5 Mar 2025 14:01:40 -0800 Subject: [PATCH 37/38] Remove examples --- includes/API.php | 35 +------ includes/Feed/AbstractFeed.php | 10 +- includes/Feed/ExampleFeed.php | 100 ------------------ includes/Feed/ExampleFeedGenerator.php | 113 --------------------- includes/Feed/ExampleFeedHandler.php | 135 ------------------------- includes/Feed/FeedManager.php | 13 +-- 6 files changed, 9 insertions(+), 397 deletions(-) delete mode 100644 includes/Feed/ExampleFeed.php delete mode 100644 includes/Feed/ExampleFeedGenerator.php delete mode 100644 includes/Feed/ExampleFeedHandler.php diff --git a/includes/API.php b/includes/API.php index bb136cdd6..2f5e231bc 100644 --- a/includes/API.php +++ b/includes/API.php @@ -102,36 +102,6 @@ protected function perform_request( $request ): API\Response { return parent::perform_request( $request ); } - /** - * Using this to perform a hard coded API request for proof of concept - * Current use case: hitting GraphPartnerIntegrationFileUpdatePost with a Magento Store for POC - * Consider keeping: For testing, good to have the ability to hit an API from local without configuring new shop and getting permissions for each dev - * @param $request - * @param $access_token - * @return Response - * @throws Framework\Plugin\Exception - * @throws Request_Limit_Reached - */ - private function perform_stub_request($request, $access_token) : API\Response { - $current_token = $this->get_access_token(); - $this->set_access_token( $access_token ); - $this->request_headers['Authorization'] = "Bearer {$access_token}"; - - $rate_limit_id = $request::get_rate_limit_id(); - $delay_timestamp = $this->get_rate_limit_delay( $rate_limit_id ); - // if there is a delayed timestamp in the future, throw an exception - if ( $delay_timestamp >= time() ) { - $this->handle_throttled_request( $rate_limit_id, $delay_timestamp ); - } else { - $this->set_rate_limit_delay( $rate_limit_id, 0 ); - } - $response = parent::perform_request( $request ); - $this->set_access_token( $current_token ); - $this->request_headers['Authorization'] = "Bearer {$current_token}"; - return $response; - } - - /** * Validates a response after it has been parsed and instantiated. * @@ -576,7 +546,7 @@ public function read_upload( string $product_feed_upload_id ) { * @throws ApiException * @throws API\Exceptions\Request_Limit_Reached */ - public function create_product_feed_upload( string $product_feed_id, array $data ) { + public function create_product_feed_upload( string $product_feed_id, array $data ): Response { $request = new API\ProductCatalog\ProductFeedUploads\Create\Request( $product_feed_id, $data ); $this->set_response_handler( API\ProductCatalog\ProductFeedUploads\Create\Response::class ); return $this->perform_request( $request ); @@ -593,8 +563,7 @@ public function create_product_feed_upload( string $product_feed_id, array $data public function create_common_data_feed_upload( string $cpi_id, array $data ): Response { $request = new API\CommonFeedUploads\Create\Request( $cpi_id, $data ); $this->set_response_handler( API\CommonFeedUploads\Create\Response::class ); - // For POC testing, use perform_stub_request with auth token of shop that can hit the endpoint - return $this->perform_stub_request( $request, 'EAACxonUmtyIBO4FIt1wXKF511sXHIbsWeWMUZB2qyaaQksZBqTvp44qpyEYjwrXJGSaZC0aizYyCKlFmbbOlFsbbpZBgybXm4gNVNzGfyx1ZARsiOprMrQe3GB9euDXl1LqBXZA5ZCA7TZCHVGDRQeAlTuWMZCgfCk3MwfniG0c85fW12LOXUCRZBGHkgwPpy2ona5R8Bv89OMKjMEV9oABfXiNMVOKwZDZD' ); + return $this->perform_request( $request ); } diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index 8b3f9c122..f9770eea4 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -20,7 +20,6 @@ * Abstract class AbstractFeed * * Provides the base functionality for handling Metadata feed requests and generation for Facebook integration. - * This class defines the structure and common methods that must be implemented by any concrete feed class. * * @package WooCommerce\Facebook\Feed * @since 3.5.0 @@ -94,8 +93,6 @@ abstract class AbstractFeed { /** * Schedules the recurring feed generation. * - * This method must be implemented by the concrete feed class, usually by providing a recurring interval - * * @since 3.5.0 */ public function schedule_feed_generation(): void { @@ -128,7 +125,7 @@ public function regenerate_feed(): void { /** * Trigger the upload flow * Once feed regenerated, trigger upload via create_upload API - * This will hit the url defined in the class and trigger the handle streaming file + * This will hit the url defined in the class and trigger handle_feed_data_request * * @since 3.5.0 */ @@ -178,14 +175,13 @@ public function get_feed_data_url(): string { * @return string */ public function get_feed_secret(): string { -/* $secret = get_option( $this->feed_url_secret_option_name, '' ); + $secret = get_option( $this->feed_url_secret_option_name, '' ); if ( ! $secret ) { $secret = wp_hash( 'example-feed-' . time() ); update_option( $this->feed_url_secret_option_name, $secret ); } - return $secret;*/ - return 'secret'; + return $secret; } /** diff --git a/includes/Feed/ExampleFeed.php b/includes/Feed/ExampleFeed.php deleted file mode 100644 index 62a61dfc8..000000000 --- a/includes/Feed/ExampleFeed.php +++ /dev/null @@ -1,100 +0,0 @@ -data_stream_name = FeedManager::EXAMPLE; - $this->gen_feed_interval = DAY_IN_SECONDS; - $this->feed_type = 'PRODUCT_RATINGS_AND_REVIEWS'; - $this->feed_url_secret_option_name = self::OPTION_FEED_URL_SECRET . $this->data_stream_name; - - $this->feed_handler = new ExampleFeedHandler( new CsvFeedFileWriter( $this->data_stream_name, self::EXAMPLE_FEED_HEADER ) ); - $scheduler = new ActionScheduler(); - $this->feed_generator = new ExampleFeedGenerator( $scheduler, $this->feed_handler->get_feed_writer(), $this->data_stream_name ); - $this->feed_generator->init(); - $this->add_hooks( Heartbeat::HOURLY ); - } - - /** - * Adds the necessary hooks for feed generation and data request handling. - * - * @param string $heartbeat The heartbeat interval for the feed generation. - * @since 3.5.0 - */ - protected function add_hooks( string $heartbeat ): void { - add_action( $heartbeat, array( $this, self::SCHEDULE_CALL_BACK ) ); - add_action( self::GENERATE_FEED_ACTION . $this->data_stream_name, array( $this, self::REGENERATE_CALL_BACK ) ); - add_action( self::FEED_GEN_COMPLETE_ACTION . $this->data_stream_name, array( $this, self::UPLOAD_CALL_BACK ) ); - add_action( self::LEGACY_API_PREFIX . self::REQUEST_FEED_ACTION . $this->data_stream_name, array( $this, self::STREAM_CALL_BACK ) ); - } - - /** - * Trigger the upload flow - * Once feed regenerated, trigger upload via create_upload API - * This will hit the url defined in the class and trigger the handle streaming file - * - * @since 3.5.0 - */ - public function send_request_to_upload_feed(): void { - $name = $this->data_stream_name; - $data = array( - 'url' => 'http://44.243.196.123/?wc-api=wc_facebook_get_feed_data_example&secret=secret', - 'feed_type' => $this->feed_type, - 'update_type' => 'CREATE', - ); - - try { - $cpi_id = \WC_Facebookcommerce::instance()->get_integration()->get_commerce_partner_integration_id(); - facebook_for_woocommerce()-> - get_api()-> - create_common_data_feed_upload( $cpi_id, $data ); - } catch ( Exception $e ) { - // Log the error and continue. - \WC_Facebookcommerce_Utils::log( "{$name} feed: Failed to create feed upload request: " . $e->getMessage() ); - } - } - - /** - * Gets the secret value that should be included in the legacy WooCommerce REST API URL. - * - * @since 3.5.0 - * @return string - */ - public function get_feed_secret(): string { - return 'secret'; - } -} diff --git a/includes/Feed/ExampleFeedGenerator.php b/includes/Feed/ExampleFeedGenerator.php deleted file mode 100644 index d0358dbac..000000000 --- a/includes/Feed/ExampleFeedGenerator.php +++ /dev/null @@ -1,113 +0,0 @@ -get_col( - $wpdb->prepare( - "SELECT post.ID - FROM {$wpdb->posts} as post - LEFT JOIN {$wpdb->posts} as parent ON post.post_parent = parent.ID - WHERE - ( post.post_type = 'product_variation' AND parent.post_status = 'publish' ) - OR - ( post.post_type = 'product' AND post.post_status = 'publish' ) - ORDER BY post.ID ASC - LIMIT %d OFFSET %d", - $this->get_batch_size(), - $this->get_query_offset( $batch_number ) - ) - ); - */ - - // For proof of concept, we will just return the review id for batch 1 - // In parent classes, batch number starts with 1. - if ( 1 === $batch_number ) { - $obj_1 = [ - 'aggregator' => 'magento', - 'store.name' => 'Default Store View', - 'store.id' => '1413745412827209', - 'store.store_urls' => "['http://35.91.150.25/']", - 'review_id' => '2', - 'rating' => '5', - 'title' => 'Great product', - 'content' => 'Very happy with this purchase. Would buy again.', - 'created_at' => '2025-01-09 18:30:43', - 'reviewer.name' => 'John Doe', - 'reviewer.reviewerID' => '1', - 'product.name' => 'Baseball', - 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', - 'product.image_urls' => "['/b/a/baseball.jpg']", - 'product.product_identifiers.skus' => "['Baseball']", - 'country' => 'US', - ]; - - $obj_2 = [ - 'aggregator' => 'magento', - 'store.name' => 'Default Store View', - 'store.id' => '1413745412827209', - 'store.store_urls' => "['http://35.91.150.25/']", - 'review_id' => '3', - 'rating' => '1', - 'title' => "Don't recommend", - 'content' => 'Unusable after just a couple games. Expected better. Would not recommend.', - 'created_at' => '2025-01-09 19:56:37', - 'reviewer.name' => 'Tim Cook', - 'reviewer.reviewerID' => '2', - 'product.name' => 'Baseball', - 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', - 'product.image_urls' => "['/b/a/baseball.jpg']", - 'product.product_identifiers.skus' => "['Baseball']", - 'country' => 'US', - ]; - - $obj_3 = [ - 'aggregator' => 'magento', - 'store.name' => 'Default Store View', - 'store.id' => '1413745412827209', - 'store.store_urls' => "['http://35.91.150.25/']", - 'review_id' => '4', - 'rating' => '4', - 'title' => 'Overall satisfied', - 'content' => 'Could have been better but overall satisfied with my purchase.', - 'created_at' => '2025-01-15 23:04:29', - 'reviewer.name' => 'Tom Manning', - 'reviewer.reviewerID' => '3', - 'product.name' => 'Baseball', - 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', - 'product.image_urls' => "['/b/a/baseball.jpg']", - 'product.product_identifiers.skus' => "['Baseball']", - 'country' => 'US', - ]; - - return array( $obj_1, $obj_2, $obj_3 ); - } else { - return array(); - } - } -} diff --git a/includes/Feed/ExampleFeedHandler.php b/includes/Feed/ExampleFeedHandler.php deleted file mode 100644 index 3830e7134..000000000 --- a/includes/Feed/ExampleFeedHandler.php +++ /dev/null @@ -1,135 +0,0 @@ -feed_writer = $feed_writer; - } - - /** - * Generate the feed file. - * - * This method is responsible for generating a feed file. - * - * @since 3.5.0 - */ - public function generate_feed_file(): void { - $this->feed_writer->write_feed_file( $this->get_feed_data() ); - /** - * Trigger upload from ExampleFeed instance - * - * @since 3.5.0 - */ - do_action( AbstractFeed::FEED_GEN_COMPLETE_ACTION .FeedManager::EXAMPLE ); - } - - /** - * Get the feed file writer instance. - * - * @return FeedFileWriter - * @since 3.5.0 - */ - public function get_feed_writer(): FeedFileWriter { - return $this->feed_writer; - } - - /** - * Get the feed data and return as array of objects. - * - * @return array - * @since 3.5.0 - */ - public function get_feed_data(): array { - $obj_1 = [ - 'aggregator' => 'magento', - 'store.name' => 'Default Store View', - 'store.id' => '1413745412827209', - 'store.store_urls' => "['http://35.91.150.25/']", - 'review_id' => '2', - 'rating' => '5', - 'title' => 'Great product', - 'content' => 'Very happy with this purchase. Would buy again.', - 'created_at' => '2025-01-09 18:30:43', - 'reviewer.name' => 'John Doe', - 'reviewer.reviewerID' => '1', - 'product.name' => 'Baseball', - 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', - 'product.image_urls' => "['/b/a/baseball.jpg']", - 'product.product_identifiers.skus' => "['Baseball']", - 'country' => 'US', - ]; - - $obj_2 = [ - 'aggregator' => 'magento', - 'store.name' => 'Default Store View', - 'store.id' => '1413745412827209', - 'store.store_urls' => "['http://35.91.150.25/']", - 'review_id' => '3', - 'rating' => '1', - 'title' => "Don't recommend", - 'content' => 'Unusable after just a couple games. Expected better. Would not recommend.', - 'created_at' => '2025-01-09 19:56:37', - 'reviewer.name' => 'Tim Cook', - 'reviewer.reviewerID' => '2', - 'product.name' => 'Baseball', - 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', - 'product.image_urls' => "['/b/a/baseball.jpg']", - 'product.product_identifiers.skus' => "['Baseball']", - 'country' => 'US', - ]; - - $obj_3 = [ - 'aggregator' => 'magento', - 'store.name' => 'Default Store View', - 'store.id' => '1413745412827209', - 'store.store_urls' => "['http://35.91.150.25/']", - 'review_id' => '4', - 'rating' => '4', - 'title' => 'Overall satisfied', - 'content' => 'Could have been better but overall satisfied with my purchase.', - 'created_at' => '2025-01-15 23:04:29', - 'reviewer.name' => 'Tom Manning', - 'reviewer.reviewerID' => '3', - 'product.name' => 'Baseball', - 'product.url' => 'http://35.91.150.25/catalog/product/view/id/12/s/baseball/', - 'product.image_urls' => "['/b/a/baseball.jpg']", - 'product.product_identifiers.skus' => "['Baseball']", - 'country' => 'US', - ]; - - return array( $obj_1, $obj_2, $obj_3 ); - } -} diff --git a/includes/Feed/FeedManager.php b/includes/Feed/FeedManager.php index f54e0d447..035385728 100644 --- a/includes/Feed/FeedManager.php +++ b/includes/Feed/FeedManager.php @@ -13,12 +13,11 @@ /** * Responsible for creating and managing feeds. * Global manipulations of the feed such as updating feed and upload ID to be made through this class. + * Add feed type names as constant strings here. * * @since 3.5.0 */ class FeedManager { - const EXAMPLE = 'example'; - /** * The list of feed types as named strings. * @@ -53,17 +52,13 @@ public function __construct() { * * @param string $data_stream_name The name of the data stream. * + * phpcs:ignore -- Method to be implemented when new feed types are added. * @return AbstractFeed The created feed instance derived from AbstractFeed. * @throws \InvalidArgumentException If the data stream doesn't correspond to a FeedType. * @since 3.5.0 */ private function create_feed( string $data_stream_name ): AbstractFeed { - switch ( $data_stream_name ) { - case self::EXAMPLE: - return new ExampleFeed(); - default: - throw new \InvalidArgumentException( 'Invalid data stream name' ); - } + throw new \InvalidArgumentException( "Invalid feed type {$data_stream_name}" ); } /** @@ -73,7 +68,7 @@ private function create_feed( string $data_stream_name ): AbstractFeed { * @since 3.5.0 */ public static function get_feed_types(): array { - return array( self::EXAMPLE ); + return array(); } /** From 0c5ba3ebffcccae3561f3bde018c5e7af5bf6d52 Mon Sep 17 00:00:00 2001 From: Julio Mendez Cabrera Date: Wed, 5 Mar 2025 14:25:40 -0800 Subject: [PATCH 38/38] Address Noah comments --- facebook-commerce.php | 11 ----------- includes/Feed/AbstractFeed.php | 5 +++-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/facebook-commerce.php b/facebook-commerce.php index febf61b7a..fefec06e7 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -2434,17 +2434,6 @@ public function get_facebook_pixel_id() { return (string) apply_filters( 'wc_facebook_pixel_id', get_option( self::SETTING_FACEBOOK_PIXEL_ID, '' ), $this ); } - /** - * Retrieve the commerce partner integration ID used in GraphPartnerIntegrationFileUpdatePost call - * Hard coded for now. Update once Mice settings are available. - * - * @return string - * @since 3.5.0 - */ - public function get_commerce_partner_integration_id(): string { - return '24316596247984028'; - } - /** * Gets the IDs of the categories to be excluded from sync. * diff --git a/includes/Feed/AbstractFeed.php b/includes/Feed/AbstractFeed.php index f9770eea4..f35ac8710 100644 --- a/includes/Feed/AbstractFeed.php +++ b/includes/Feed/AbstractFeed.php @@ -110,6 +110,8 @@ public function schedule_feed_generation(): void { /** * Regenerates the example feed based on the defined schedule. + * New style feed will use the FeedGenerator to queue the feed generation. Use for batched feed generation. + * Old style feed will use the FeedHandler to generate the feed file. Use if batch not needed or new style not enabled. * * @since 3.5.0 */ @@ -138,7 +140,7 @@ public function send_request_to_upload_feed(): void { ); try { - $cpi_id = \WC_Facebookcommerce::instance()->get_integration()->get_commerce_partner_integration_id(); + $cpi_id = get_option( 'wc_facebook_commerce_partner_integration_id', '' ); facebook_for_woocommerce()-> get_api()-> create_common_data_feed_upload( $cpi_id, $data ); @@ -212,7 +214,6 @@ public function handle_feed_data_request(): void { // bail early if the file can't be read. if ( ! is_readable( $file_path ) ) { - throw new PluginException( "{$name}: File at path ' . $file_path . ' is not readable.", 404 ); }