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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions includes/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,18 @@ public function read_feed( string $product_feed_id ) {
return $this->perform_request( $request );
}

/**
* @param string $product_catalog_id Facebook Product Catalog ID.
* @return Response
* @throws ApiException
* @throws API\Exceptions\Request_Limit_Reached
*/
public function create_feed( string $product_catalog_id, array $data ) {
$request = new API\ProductCatalog\ProductFeeds\Create\Request( $product_catalog_id, $data );
$this->set_response_handler( API\ProductCatalog\ProductFeeds\Create\Response::class );
return $this->perform_request( $request );
}


/**
* @param string $product_feed_upload_id
Expand All @@ -528,6 +540,18 @@ public function read_upload( string $product_feed_upload_id ) {
return $this->perform_request( $request );
}

/**
* @param string $product_feed_id Facebook Product Feed ID.
* @return Response
* @throws ApiException
* @throws API\Exceptions\Request_Limit_Reached
*/
public function create_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 );
}


/**
* @param string $external_merchant_settings_id
Expand Down
25 changes: 25 additions & 0 deletions includes/API/ProductCatalog/ProductFeedUploads/Create/Request.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare( strict_types=1 );

namespace WooCommerce\Facebook\API\ProductCatalog\ProductFeedUploads\Create;

use WooCommerce\Facebook\API\Request as ApiRequest;

defined( 'ABSPATH' ) || exit;

/**
* Request object for Product Catalog > Product Feed Upload > Create Graph Api.
*
* @link https://developers.facebook.com/docs/marketing-api/reference/product-feed/uploads/#Creating
*/
class Request extends ApiRequest {

/**
* @param string $product_feed_id Facebook Product Feed ID.
* @param array $data Facebook Product Feed Data.
*/
public function __construct( string $product_feed_id, array $data ) {
parent::__construct( "/{$product_feed_id}/uploads", 'POST' );
parent::set_data( $data );
}
}
16 changes: 16 additions & 0 deletions includes/API/ProductCatalog/ProductFeedUploads/Create/Response.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
declare( strict_types=1 );

namespace WooCommerce\Facebook\API\ProductCatalog\ProductFeedUploads\Create;

use WooCommerce\Facebook\API\Response as ApiResponse;

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.
*/
class Response extends ApiResponse {}
2 changes: 1 addition & 1 deletion includes/API/ProductCatalog/ProductFeeds/Read/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ class Request extends ApiRequest {
* @param string $product_feed_id Facebook Product Feed ID.
*/
public function __construct( string $product_feed_id ) {
parent::__construct( "/{$product_feed_id}/?fields=created_time,latest_upload,product_count,schedule,update_schedule", 'GET' );
parent::__construct( "/{$product_feed_id}/?fields=created_time,latest_upload,product_count,schedule,update_schedule,name", 'GET' );
}
}
2 changes: 2 additions & 0 deletions includes/Jobs/GenerateProductFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ protected function handle_end() {
$feed_handler = new \WC_Facebook_Product_Feed();
$feed_handler->rename_temporary_feed_file_to_final_feed_file();
facebook_for_woocommerce()->get_tracker()->save_batch_generation_time();

do_action('wc_facebook_feed_generation_completed');
}

/**
Expand Down
195 changes: 194 additions & 1 deletion includes/Products/Feed.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

defined( 'ABSPATH' ) || exit;

use Error;
use Exception;
use WC_Facebookcommerce_Utils;
use WooCommerce\Facebook\Framework\Helper;
use WooCommerce\Facebook\Utilities\Heartbeat;
use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException;
Expand All @@ -36,6 +39,8 @@ class Feed {
/** @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';

/** @var string the feed name for creating a new feed by this plugin */
const FEED_NAME = 'Product Feed by Facebook for WooCommerce plugin. DO NOT DELETE.';

/**
* Feed constructor.
Expand All @@ -62,6 +67,9 @@ private function add_hooks() {

// handle the feed data request
add_action( 'woocommerce_api_' . self::REQUEST_FEED_ACTION, array( $this, 'handle_feed_data_request' ) );

// Send request for feed one time upload after feed file generated
add_action( 'wc_facebook_feed_generation_completed', array( $this, 'send_request_to_upload_feed' ) );
}


Expand Down Expand Up @@ -170,7 +178,7 @@ public function schedule_feed_generation() {
* @since 1.11.0
* @since 2.5.0 Feed generation interval increased to 24h.
*
* @param int $interval the frequency with which the product feed data is generated, in seconds. Defaults to every 15 minutes.
* @param int $interval the frequency with which the product feed data is generated, in seconds.
*/
$interval = apply_filters( 'wc_facebook_feed_generation_interval', DAY_IN_SECONDS );
if ( ! as_next_scheduled_action( self::GENERATE_FEED_ACTION ) ) {
Expand All @@ -179,6 +187,191 @@ public function schedule_feed_generation() {
}


/**
Comment thread
mshymon marked this conversation as resolved.
* Sends request to Meta to start a one-time feed file upload session.
*
* @internal
*/
public function send_request_to_upload_feed() {
$feed_id = self::retrieve_or_create_integration_feed_id();
Comment thread
mshymon marked this conversation as resolved.
if ( empty( $feed_id ) ) {
WC_Facebookcommerce_Utils::log( 'Feed: integration feed ID is null or empty, feed will not be uploaded.' );
return;
}

$data = [
'url' => Feed::get_feed_data_url(),
];

try {
facebook_for_woocommerce()->get_api()->create_upload( $feed_id, $data );
} catch ( Exception $exception ) {
facebook_for_woocommerce()->log( 'Failed to create feed upload request: ' . $exception->getMessage() );
}
}

/**
* Retrieves or creates an integration feed ID
*
* @return string the integration feed ID
*
* @internal
*/
public function retrieve_or_create_integration_feed_id() {
// Step 1 - Get feed ID if it is already available in local cache
$feed_id = facebook_for_woocommerce()->get_integration()->get_feed_id();
if ( $feed_id ) {
if ( self::validate_feed_exists($feed_id) ) {
WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', from local cache was validated.');
return $feed_id;
} else {
WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', from local cache was invalidated.');
}
}

// Step 2 - Query feeds data from Meta and filter the right one
$feed_id = self::query_and_filter_integration_feed_id();
Comment thread
mshymon marked this conversation as resolved.
if ( $feed_id ) {
facebook_for_woocommerce()->get_integration()->update_feed_id($feed_id);
WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', queried and filtered from Meta API.');
return $feed_id;
}

// Step 3 - Create a new feed
$feed_id = self::create_feed_id();
if ( $feed_id ) {
facebook_for_woocommerce()->get_integration()->update_feed_id($feed_id);
WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', created a new feed via Meta API.');
return $feed_id;
}

Comment thread
mshymon marked this conversation as resolved.
return '';
}

/**
* Validates that provided feed ID still exists on the Meta side
*
* @param string $feed_id the feed ID
*
* @return bool true if the feed ID is valid
*
* @internal
*/
private function validate_feed_exists($feed_id) {
try {
$catalog_id = facebook_for_woocommerce()->get_integration()->get_product_catalog_id();
if ( '' === $catalog_id ) {
throw new Error( 'No catalog ID' );
}
$feed_nodes = facebook_for_woocommerce()->get_api()->read_feeds( $catalog_id )->data;
} catch ( Exception $e ) {
$message = sprintf( 'There was an error trying to get feed nodes for catalog: %s', $e->getMessage() );
WC_Facebookcommerce_Utils::log( $message );
return '';
}

foreach ( $feed_nodes as $feed ) {
if ($feed['id'] == $feed_id) {
return true;
}
}

return false;
}

/**
* Queries existing feeds for the integration catalog and filters
* the plugin integration feed ID
*
* @return string the integration feed ID
*
* @internal
*/
private function query_and_filter_integration_feed_id() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method seems like a lot of logic
Can we have it broken out a bit

try {
$catalog_id = facebook_for_woocommerce()->get_integration()->get_product_catalog_id();
if ( '' === $catalog_id ) {
throw new Error( 'No catalog ID' );
}
$feed_nodes = facebook_for_woocommerce()->get_api()->read_feeds( $catalog_id )->data;
} catch ( Exception $e ) {
$message = sprintf( 'There was an error trying to get feed nodes for catalog: %s', $e->getMessage() );
WC_Facebookcommerce_Utils::log( $message );
return '';
}

if ( empty( $feed_nodes ) ) {
return '';
}

try {
$catalog = facebook_for_woocommerce()->get_api()->get_catalog( $catalog_id );
} catch ( Exception $e ) {
$message = sprintf( 'There was an error trying to get a catalog: %s', $e->getMessage() );
WC_Facebookcommerce_Utils::log( $message );
}

/*
We need to detect which feed is the one that was created for Facebook for WooCommerce plugin usage.

We are detecting based on the name.
- Option 1. Plugin can create this feed name currently.
- Option 2 and 3. FBE creates a catalog with feed name '{catalog name} - Feed' or '{catalog name} – Feed' (short vs long dash)
- Option 4. Plugin used to create a feed name 'Initial product sync from WooCommerce. DO NOT DELETE.'
*/
foreach ( $feed_nodes as $feed ) {
try {
$feed_metadata = facebook_for_woocommerce()->get_api()->read_feed( $feed['id'] );
} catch ( Exception $e ) {
$message = sprintf( 'There was an error trying to get feed metadata: %s', $e->getMessage() );
WC_Facebookcommerce_Utils::log( $message );
continue;
}

$woo_feed_name_option_1 = self::FEED_NAME;
$woo_feed_name_option_2 = sprintf( '%s - Feed', $catalog['name'] );
$woo_feed_name_option_3 = sprintf( '%s – Feed', $catalog['name'] );
$woo_feed_name_option_4 = 'Initial product sync from WooCommerce. DO NOT DELETE.';

if ( $feed_metadata['name'] === $woo_feed_name_option_1 ||
$feed_metadata['name'] === $woo_feed_name_option_2 ||
$feed_metadata['name'] === $woo_feed_name_option_3 ||
$feed_metadata['name'] === $woo_feed_name_option_4 ) {
return $feed['id'];
}
}

return '';
}

/**
* Makes a request to Meta to create a new feed
*
* @return string the integration feed ID
*
* @internal
*/
private function create_feed_id() {
try {
$catalog_id = facebook_for_woocommerce()->get_integration()->get_product_catalog_id();
if ( '' === $catalog_id ) {
throw new Error( 'No catalog ID' );
}

$data = [
'name' => self::FEED_NAME,
];

$feed = facebook_for_woocommerce()->get_api()->create_feed( $catalog_id, $data );
return $feed['id'];
} catch ( Exception $exception ) {
facebook_for_woocommerce()->log( 'Could not create a feed: ' . $exception->getMessage() );
}

return '';
}


/**
* Checks whether fpassthru has been disabled in PHP.
*
Expand Down
6 changes: 2 additions & 4 deletions includes/fbproduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,6 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel
'custom_fields' => $custom_fields,
);
$product_data = $this->add_sale_price( $product_data, true );
$gpc_field_name = 'google_product_category';
if ( ! empty( $video_urls ) ) {
$product_data['video'] = $video_urls;
}
Expand Down Expand Up @@ -883,12 +882,11 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel
$product_data['video'] = $video_urls;
}
$product_data = $this->add_sale_price( $product_data );
$gpc_field_name = 'category';
}//end if

$google_product_category = Products::get_google_product_category_id( $this->woo_product );
if ( $google_product_category ) {
$product_data[ $gpc_field_name ] = $google_product_category;
$product_data[ 'google_product_category' ] = $google_product_category;
}

// Currently only items batch and feed support enhanced catalog fields
Expand Down Expand Up @@ -937,7 +935,7 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel
if ( self::PRODUCT_PREP_TYPE_FEED !== $type_to_prepare_for ) {
$this->prepare_variants_for_item( $product_data );
} elseif (
WC_Facebookcommerce_Utils::is_all_caps( $product_data['description'] )
WC_Facebookcommerce_Utils::is_all_caps( $product_data['description'] )
) {
$product_data['description'] =
mb_strtolower( $product_data['description'] );
Expand Down
Loading