Skip to content

Commit

Permalink
Merge pull request #2202 from woocommerce/add/hooks-api-pull-metas
Browse files Browse the repository at this point in the history
Scheduled notifications using meta handler
  • Loading branch information
puntope authored Jan 30, 2024
2 parents 96fddfb + 2bb00ca commit bd3e0b6
Show file tree
Hide file tree
Showing 11 changed files with 756 additions and 88 deletions.
66 changes: 48 additions & 18 deletions src/Google/NotificationsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,35 @@
* Class NotificationsService
* This class implements a service to Notify a partner about Shop Data Updates
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Google\Notifications
* @since x.x.x
* @package Automattic\WooCommerce\GoogleListingsAndAds\Google
*/
class NotificationsService implements Service {

// List of Topics to be used.
public const TOPIC_PRODUCT_CREATED = 'product.created';
public const TOPIC_PRODUCT_DELETED = 'product.deleted';
public const TOPIC_PRODUCT_UPDATED = 'product.updated';
public const TOPIC_COUPON_CREATED = 'coupon.created';
public const TOPIC_COUPON_DELETED = 'coupon.deleted';
public const TOPIC_COUPON_UPDATED = 'coupon.updated';
public const TOPIC_PRODUCT_CREATED = 'product.create';
public const TOPIC_PRODUCT_DELETED = 'product.delete';
public const TOPIC_PRODUCT_UPDATED = 'product.update';
public const TOPIC_COUPON_CREATED = 'coupon.create';
public const TOPIC_COUPON_DELETED = 'coupon.delete';
public const TOPIC_COUPON_UPDATED = 'coupon.update';
public const TOPIC_SHIPPING_SAVED = 'action.woocommerce_after_shipping_zone_object_save';
public const TOPIC_SHIPPING_DELETED = 'action.woocommerce_delete_shipping_zone';

/**
* The route to send the notification
* The url to send the notification
*
* @var string $route
* @var string $notification_url
*/
private $route;
private $notification_url;


/**
* Class constructor
*/
public function __construct() {
$blog_id = Jetpack_Options::get_option( 'id' );
$this->route = "https://public-api.wordpress.com/wpcom/v2/sites/{$blog_id}/partners/google/notifications";
$blog_id = Jetpack_Options::get_option( 'id' );
$this->notification_url = "https://public-api.wordpress.com/wpcom/v2/sites/{$blog_id}/partners/google/notifications";
}

/**
Expand All @@ -50,7 +52,26 @@ public function __construct() {
* @param string $topic
* @return bool True is the notification is successful. False otherwise.
*/
public function notify( int $item_id, string $topic ) {
public function notify( int $item_id, string $topic ): bool {
/**
* Allow users to disable the notification request.
*
* @since x.x.x
*
* @param bool $value The current filter value. True by default.
* @param int $item_id The item_id for the notification.
* @param string $topic The topic for the notification.
*/
if ( ! apply_filters( 'woocommerce_gla_notify', true, $item_id, $topic ) ) {
return false;
}

do_action(
'woocommerce_gla_debug_message',
sprintf( 'Notification - Item ID: %d - Topic: %s', $item_id, $topic ),
__METHOD__
);

$remote_args = [
'method' => 'POST',
'timeout' => 30,
Expand All @@ -60,12 +81,12 @@ public function notify( int $item_id, string $topic ) {
'body' => [
'item_id' => $item_id,
],
'url' => $this->get_route(),
'url' => $this->get_notification_url(),
];

$response = $this->do_request( $remote_args );

if( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) >= 400 ) {
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) >= 400 ) {
$error = is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_body( $response );
$this->notification_error( $item_id, $topic, $error );
return false;
Expand Down Expand Up @@ -95,7 +116,7 @@ private function notification_error( int $item_id, string $topic, string $error
* @param array $args
* @return array|\WP_Error
*/
protected function do_request( $args ) {
protected function do_request( array $args ): \WP_Error|array {
return Client::remote_request( $args, wp_json_encode( $args['body'] ) );
}

Expand All @@ -104,7 +125,16 @@ protected function do_request( $args ) {
*
* @return string The route.
*/
public function get_route(): string {
return $this->route;
public function get_notification_url(): string {
return $this->notification_url;
}

/**
* If the Notifications are enabled
*
* @return bool
*/
public function is_enabled(): bool {
return apply_filters( 'woocommerce_gla_notifications_enabled', true );
}
}
8 changes: 8 additions & 0 deletions src/Internal/DependencyManagement/JobServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobInitializer;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications\ProductNotificationJob;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ProductSyncerJobInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ProductSyncStats;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ResubmitExpiringProducts;
Expand Down Expand Up @@ -106,6 +107,13 @@ public function register(): void {
$this->share_coupon_syncer_job( UpdateCoupon::class );
$this->share_coupon_syncer_job( DeleteCoupon::class );

// share product notifications job
$this->share_action_scheduler_job(
ProductNotificationJob::class,
NotificationsService::class,
ProductHelper::class
);

$this->share_with_tags(
JobRepository::class,
JobInterface::class
Expand Down
133 changes: 133 additions & 0 deletions src/Jobs/Notifications/ProductNotificationJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);

namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;

use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\AbstractActionSchedulerJob;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ActionSchedulerJobMonitor;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\NotificationStatus;


defined( 'ABSPATH' ) || exit;

/**
* Class ProductNotificationJob
* Class for all the Product Notifications Jobs
*
* @since x.x.x
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
class ProductNotificationJob extends AbstractActionSchedulerJob implements JobInterface {

/**
* @var NotificationsService $notifications_service
*/
protected $notifications_service;

/**
* @var ProductHelper $product_helper
*/
protected $product_helper;

/**
* Notifications Jobs constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param NotificationsService $notifications_service
* @param ProductHelper $product_helper
*/
public function __construct(
ActionSchedulerInterface $action_scheduler,
ActionSchedulerJobMonitor $monitor,
NotificationsService $notifications_service,
ProductHelper $product_helper
) {
$this->notifications_service = $notifications_service;
$this->product_helper = $product_helper;
parent::__construct( $action_scheduler, $monitor );
}

/**
* Get the job name
*
* @return string
*/
public function get_name(): string {
return 'notifications/products';
}


/**
* Logic when processing the items
*
* @param array $args Arguments with the item id and the topic
*/
protected function process_items( array $args ): void {
if ( ! isset( $args[0] ) || ! isset( $args[1] ) ) {
return;
}

$item = $args[0];
$topic = $args[1];

if ( $this->notifications_service->notify( $item, $topic ) ) {
$this->set_status( $item, $this->get_after_notification_status( $topic ) );
}
}

/**
* Schedule the Product Notification Job
*
* @param array $args
*/
public function schedule( array $args = [] ): void {
/**
* Allow users to disable the notification job schedule.
*
* @since x.x.x
*
* @param bool $value The current filter value. By default, it is the result of `$this->can_schedule` function.
* @param array $args The arguments for the schedule function with the item id and the topic.
*/
$can_schedule = apply_filters( 'woocommerce_gla_product_notification_job_can_schedule', $this->can_schedule( [ $args ] ), $args );

if ( $can_schedule ) {
$this->action_scheduler->schedule_immediate(
$this->get_process_item_hook(),
[ $args ]
);
}
}

/**
* Set the notification status for a product.
*
* @param int $product_id
* @param string $status
*/
protected function set_status( int $product_id, string $status ): void {
$product = $this->product_helper->get_wc_product( $product_id );
$this->product_helper->set_notification_status( $product, $status );
}

/**
* Get the Notification Status after the notification happens
*
* @param string $topic
* @return string
*/
protected function get_after_notification_status( string $topic ): string {
if ( str_contains( $topic, '.create' ) ) {
return NotificationStatus::NOTIFICATION_CREATED;
} elseif ( str_contains( $topic, '.delete' ) ) {
return NotificationStatus::NOTIFICATION_DELETED;
} else {
return NotificationStatus::NOTIFICATION_UPDATED;
}
}
}
90 changes: 90 additions & 0 deletions src/Product/ProductHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\ChannelVisibility;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\NotificationStatus;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\SyncStatus;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\ShoppingContent\Product as GoogleProduct;
use WC_Product;
Expand Down Expand Up @@ -332,6 +333,95 @@ public function is_product_synced( WC_Product $product ): bool {
return ! empty( $synced_at ) && ! empty( $google_ids );
}

/**
* Indicates if a product is ready for sending Notifications.
* A product is ready to send notifications if DONT_SYNC_AND_SHOW is not enabled and the post status is publish.
*
* @param WC_Product $product
*
* @return bool
*/
public function is_ready_to_notify( WC_Product $product ): bool {
$is_ready = ChannelVisibility::DONT_SYNC_AND_SHOW !== $this->get_channel_visibility( $product ) &&
$product->get_status() === 'publish' &&
in_array( $product->get_type(), ProductSyncer::get_supported_product_types(), true );

/**
* Allow users to filter if a product is ready to notify.
*
* @since x.x.x
*
* @param bool $value The current filter value.
* @param WC_Product $product The product for the notification.
*/
return apply_filters( 'woocommerce_gla_is_ready_to_notify', $is_ready, $product );
}

/**
* Indicates if a product is ready for sending a create Notification.
* A product is ready to send create notifications if is ready to notify and has not sent create notification yet.
*
* @param WC_Product $product
*
* @return bool
*/
public function should_trigger_create_notification( WC_Product $product ): bool {
return $this->is_ready_to_notify( $product ) && ! $this->has_notified_creation( $product );
}

/**
* Indicates if a product is ready for sending an update Notification.
* A product is ready to send update notifications if is ready to notify and has sent create notification already.
*
* @param WC_Product $product
*
* @return bool
*/
public function should_trigger_update_notification( WC_Product $product ): bool {
return $this->is_ready_to_notify( $product ) && $this->has_notified_creation( $product );
}

/**
* Indicates if a product is ready for sending a delete Notification.
* A product is ready to send delete notifications if it is not ready to notify and has sent create notification already.
*
* @param WC_Product $product
*
* @return bool
*/
public function should_trigger_delete_notification( WC_Product $product ): bool {
return ! $this->is_ready_to_notify( $product ) && $this->has_notified_creation( $product );
}

/**
* Indicates if a product was already notified about its creation.
* Notice we consider synced products in MC as notified for creation.
*
* @param WC_Product $product
*
* @return bool
*/
public function has_notified_creation( WC_Product $product ): bool {
$valid_has_notified_creation_statuses = [
NotificationStatus::NOTIFICATION_PENDING_CREATE,
NotificationStatus::NOTIFICATION_CREATED,
NotificationStatus::NOTIFICATION_UPDATED,
NotificationStatus::NOTIFICATION_PENDING_UPDATE
];

return in_array( $this->meta_handler->get_notification_status( $product ), $valid_has_notified_creation_statuses, true ) || $this->is_product_synced( $product );
}

/**
* Set the notification status for a WooCommerce product.
*
* @param WC_Product $product
* @param string $status
*/
public function set_notification_status( WC_Product $product, $status ): void {
$this->meta_handler->update_notification_status( $product, $status );
}

/**
* @param WC_Product $product
*
Expand Down
Loading

0 comments on commit bd3e0b6

Please sign in to comment.