diff --git a/src/Feed/AsyncGenerator.php b/src/Feed/AsyncGenerator.php index caa23671..afbc5568 100644 --- a/src/Feed/AsyncGenerator.php +++ b/src/Feed/AsyncGenerator.php @@ -63,6 +63,20 @@ public function generate_feed_part( $page, $post_per_page ) { // Process products returned by the query and reschedule the action for the next page. $path = sprintf( '%s/%s', ShoppingFeedHelper::get_feed_parts_directory(), 'part_' . $page ); $products_list = Products::get_instance()->format_products( $products ); + + /** + * Filter the products list before writing to the feed + * + * This filter allows clients to modify the products list before it's passed to ProductGenerator::write() + * For example, to duplicate products based on custom attributes like 'backmarketid' + * + * @param \Generator $products_list The generator yielding product arrays + * @param int $page Current page number + * @param int $post_per_page Number of products per page + * @param array $products Original WC_Product objects + */ + $products_list = apply_filters( 'shopping_feed_products_list_before_write', $products_list, $page, $post_per_page, $products ); + try { $this->generator = new ProductGenerator(); $this->generator->setPlatform( (string) $page, (string) $page ); diff --git a/src/Feed/Generator.php b/src/Feed/Generator.php index b0a55bda..9eb98994 100644 --- a/src/Feed/Generator.php +++ b/src/Feed/Generator.php @@ -291,6 +291,19 @@ public function render( $no_cache = false ) { public function generate() { $products_list = Products::get_instance()->get_list(); + /** + * Filter the products list before writing to the feed + * + * This filter allows clients to modify the products list before it's passed to ProductGenerator::write() + * For example, to duplicate products based on custom attributes like 'backmarketid' + * + * @param \Generator $products_list The generator yielding product arrays + * @param int $page Current page number (0 for synchronous generation) + * @param int $post_per_page Number of products per page (-1 for synchronous generation) + * @param array $products Original WC_Product objects (empty array for synchronous generation) + */ + $products_list = apply_filters( 'shopping_feed_products_list_before_write', $products_list, 0, -1, array() ); + try { $this->generator->write( $products_list ); $uri = Uri::get_full_path(); diff --git a/src/Orders/Order/Products.php b/src/Orders/Order/Products.php index ee701a9a..166645c7 100644 --- a/src/Orders/Order/Products.php +++ b/src/Orders/Order/Products.php @@ -77,7 +77,24 @@ private function mapping_product( $sf_product ) { $wc_product_id = $sf_product->getReference(); if ( 'sku' === $product_identifier ) { - $wc_product_id = wc_get_product_id_by_sku( $wc_product_id ); + /** + * Filter to allow custom SKU mapping for order processing + * + * This filter allows clients to map modified SKUs back to their original form + * For example, to handle SKUs like "ORIGINAL_SKU_1_BACKMARKET_ID" and extract "ORIGINAL_SKU" + * + * @param int|false $wc_product_id The WooCommerce product ID (false if not found) + * @param string $sf_reference The original reference from ShoppingFeed + * @param OrderItem $sf_product The ShoppingFeed product object + * @param string $product_identifier The product identifier type ('id' or 'sku') + */ + $wc_product_id = apply_filters( 'shopping_feed_order_product_id_mapping', $wc_product_id, $sf_product->getReference(), $sf_product, $product_identifier ); + + // If the filter didn't return a valid product ID, try the default mapping + if ( ! $wc_product_id ) { + $wc_product_id = wc_get_product_id_by_sku( $sf_product->getReference() ); + } + if ( ! $wc_product_id ) { return array(); } diff --git a/src/Orders/Order/Shipping.php b/src/Orders/Order/Shipping.php index 05c25975..cb8cac47 100644 --- a/src/Orders/Order/Shipping.php +++ b/src/Orders/Order/Shipping.php @@ -98,7 +98,17 @@ private function set_shipping_rate() { if ( empty( $this->method ) && empty( $default_shipping_method ) ) { return; } + $shipping_rate = ShoppingFeedHelper::get_wc_shipping_from_sf_carrier( $this->method ); + + /** + * Filter the shipping method data used when creating the shipping rates. + * + * @param array $shipping_rate the shipping method data. + * @param OrderResource $sf_order the ShoppingFeed order. + */ + $shipping_rate = apply_filters( 'shopping_feed_order_shipping_rate', $shipping_rate, $this->sf_order ); + if ( empty( $shipping_rate ) ) { $shipping_rate = $default_shipping_method; } diff --git a/src/ShoppingFeedHelper.php b/src/ShoppingFeedHelper.php index 47301304..d1fbac63 100644 --- a/src/ShoppingFeedHelper.php +++ b/src/ShoppingFeedHelper.php @@ -7,7 +7,6 @@ use ShoppingFeed\ShoppingFeedWC\Admin\Options; use ShoppingFeed\ShoppingFeedWC\ShipmentTracking\ShipmentTrackingManager; -use ShoppingFeed\ShoppingFeedWC\ShipmentTracking\ShipmentTrackingProvider; use ShoppingFeed\ShoppingFeedWC\Url\Rewrite; use WC_Logger; @@ -56,19 +55,31 @@ public static function get_wc_version() { } /** - * Return the feed's directory + * Return the feed's directory. + * * @return string */ public static function get_feed_directory() { - return SF_FEED_DIR; + /** + * Filter the path to the directory where product feeds are stored. + * + * @param string $path Path to the directory. + */ + return (string) apply_filters( 'shopping_feed_feed_directory_path', SF_FEED_DIR ); } /** - * Return the feed's parts directory + * Return the feed's parts directory. + * * @return string */ public static function get_feed_parts_directory() { - return SF_FEED_PARTS_DIR; + /** + * Filter the path to the directory where product feeds parts are stored. + * + * @param string $path Path to the directory. + */ + return (string) apply_filters( 'shopping_feed_feed_parts_directory_path', SF_FEED_PARTS_DIR ); } /** @@ -92,11 +103,21 @@ public static function get_feed_skeleton() { } /** - * Return the feed's file name + * Return the feed's file name. + * + * The filename doesn't contain the file extension. + * * @return string */ public static function get_feed_filename() { - return 'products'; + /** + * Filter the product feed's filename. + * + * The filename must not contain the file extension. + * + * @param string $path Feed's filename. + */ + return (string) apply_filters( 'shopping_feed_feed_filename', 'products' ); } /** diff --git a/tests/wpunit/Feed/HelperFunctionsTest.php b/tests/wpunit/Feed/HelperFunctionsTest.php new file mode 100644 index 00000000..e3d886ff --- /dev/null +++ b/tests/wpunit/Feed/HelperFunctionsTest.php @@ -0,0 +1,83 @@ +assertEquals( + self::$upload_dir['basedir'] . '/shopping-feed', + ShoppingFeedHelper::get_feed_directory() + ); + } + + public function test_get_feed_directory_filter() { + $custom_feed_directory = '/tmp/shopping-feed'; + + add_filter( + 'shopping_feed_feed_directory_path', + function ( $dir ) use ( $custom_feed_directory ) { + return $custom_feed_directory; + } + ); + + $this->assertEquals( + $custom_feed_directory, + ShoppingFeedHelper::get_feed_directory() + ); + } + + public function test_get_feed_part_directory() { + $this->assertEquals( + self::$upload_dir['basedir'] . '/shopping-feed/parts', + ShoppingFeedHelper::get_feed_parts_directory() + ); + } + + public function test_get_feed_part_directory_filter() { + $custom_feed_directory = '/tmp/shopping-feed-parts'; + + add_filter( + 'shopping_feed_feed_parts_directory_path', + function ( $dir ) use ( $custom_feed_directory ) { + return $custom_feed_directory; + } + ); + + $this->assertEquals( + $custom_feed_directory, + ShoppingFeedHelper::get_feed_parts_directory() + ); + } + + public function test_get_feed_filename() { + $this->assertEquals( + 'products', + ShoppingFeedHelper::get_feed_filename() + ); + } + + public function test_get_feed_filename_filter() { + $custom_feed_filename = 'custom-products'; + + add_filter( + 'shopping_feed_feed_filename', + function ( $filename ) use ( $custom_feed_filename ) { + return $custom_feed_filename; + } + ); + + $this->assertEquals( + $custom_feed_filename, + ShoppingFeedHelper::get_feed_filename() + ); + } +} \ No newline at end of file