File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/Site.tar
Controllers/Ads/AccountController.php 0000644 00000013122 15155033414 0013732 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\Ads\AccountService;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class AccountController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class AccountController extends BaseController {
/**
* Service used to access / update Ads account data.
*
* @var AccountService
*/
protected $account;
/**
* AccountController constructor.
*
* @param RESTServer $server
* @param AccountService $account
*/
public function __construct( RESTServer $server, AccountService $account ) {
parent::__construct( $server );
$this->account = $account;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'ads/accounts',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_accounts_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->create_or_link_account_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'ads/connection',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_connected_ads_account_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->disconnect_ads_account_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
$this->register_route(
'ads/billing-status',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_billing_status_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
$this->register_route(
'ads/account-status',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_ads_account_has_access(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback function for the list accounts request.
*
* @return callable
*/
protected function get_accounts_callback(): callable {
return function () {
try {
return new Response( $this->account->get_accounts() );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for creating or linking an account.
*
* @return callable
*/
protected function create_or_link_account_callback(): callable {
return function ( Request $request ) {
try {
$link_id = absint( $request['id'] );
if ( $link_id ) {
$this->account->use_existing_account( $link_id );
}
$account_data = $this->account->setup_account();
return $this->prepare_item_for_response( $account_data, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for the connected ads account.
*
* @return callable
*/
protected function get_connected_ads_account_callback(): callable {
return function () {
return $this->account->get_connected_account();
};
}
/**
* Get the callback function for disconnecting a merchant.
*
* @return callable
*/
protected function disconnect_ads_account_callback(): callable {
return function () {
$this->account->disconnect();
return [
'status' => 'success',
'message' => __( 'Successfully disconnected.', 'google-listings-and-ads' ),
];
};
}
/**
* Get the callback function for retrieving the billing setup status.
*
* @return callable
*/
protected function get_billing_status_callback(): callable {
return function () {
return $this->account->get_billing_status();
};
}
/**
* Get the callback function for retrieving the account access status for ads.
*
* @return callable
*/
protected function get_ads_account_has_access(): callable {
return function () {
try {
return $this->account->get_ads_account_has_access();
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'id' => [
'type' => 'number',
'description' => __( 'Google Ads Account ID.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => false,
],
'billing_url' => [
'type' => 'string',
'description' => __( 'Billing Flow URL.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'account';
}
}
Controllers/Ads/AssetGroupController.php 0000644 00000022143 15155033414 0014435 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\AdsAssetGroup;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\AssetFieldType;
use WP_REST_Request as Request;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class for handling API requests related to the asset groups.
* See https://developers.google.com/google-ads/api/reference/rpc/v18/AssetGroup
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class AssetGroupController extends BaseController {
/**
* The AdsAssetGroup class.
*
* @var AdsAssetGroup $ads_asset_group
*/
protected $ads_asset_group;
/**
* AssetGroupController constructor.
*
* @param RESTServer $rest_server
* @param AdsAssetGroup $ads_asset_group
*/
public function __construct( RESTServer $rest_server, AdsAssetGroup $ads_asset_group ) {
parent::__construct( $rest_server );
$this->ads_asset_group = $ads_asset_group;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'ads/campaigns/asset-groups/(?P<id>[\d]+)',
[
[
'methods' => TransportMethods::EDITABLE,
'callback' => $this->edit_asset_group_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->edit_asset_group_params(),
],
]
);
$this->register_route(
'ads/campaigns/asset-groups',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_asset_groups_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_asset_group_params(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->create_asset_group_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_asset_group_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the schema for the asset group.
*
* @return array The asset group schema.
*/
public function get_asset_group_fields(): array {
return [
'final_url' => [
'type' => 'string',
'description' => __( 'Final URL.', 'google-listings-and-ads' ),
],
'path1' => [
'type' => 'string',
'description' => __( 'Asset Group path 1.', 'google-listings-and-ads' ),
],
'path2' => [
'type' => 'string',
'description' => __( 'Asset Group path 2.', 'google-listings-and-ads' ),
],
];
}
/**
* Get the edit asset group params params to update an asset group.
*
* @return array The edit asset group params.
*/
public function edit_asset_group_params(): array {
return array_merge(
[
'id' => [
'description' => __( 'Asset Group ID.', 'google-listings-and-ads' ),
'type' => 'integer',
'required' => true,
],
'assets' => [
'type' => 'array',
'description' => __( 'List of asset to be edited.', 'google-listings-and-ads' ),
'items' => $this->get_schema_asset(),
'default' => [],
],
],
$this->get_asset_group_fields()
);
}
/**
* Get the assets groups params.
*
* @return array
*/
public function get_asset_group_params(): array {
return [
'campaign_id' => [
'description' => __( 'Campaign ID.', 'google-listings-and-ads' ),
'type' => 'integer',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
];
}
/**
* Get Asset Groups by Campaign ID.
*
* @return callable
*/
protected function get_asset_groups_callback(): callable {
return function ( Request $request ) {
try {
$campaign_id = $request->get_param( 'campaign_id' );
return array_map(
function ( $item ) use ( $request ) {
$data = $this->prepare_item_for_response( $item, $request );
return $this->prepare_response_for_collection( $data );
},
$this->ads_asset_group->get_asset_groups_by_campaign_id( $campaign_id )
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Create asset group.
*
* @return callable
*/
public function create_asset_group_callback(): callable {
return function ( Request $request ) {
try {
$asset_group_id = $this->ads_asset_group->create_asset_group( $request->get_param( 'campaign_id' ) );
return [
'status' => 'success',
'message' => __( 'Successfully created asset group.', 'google-listings-and-ads' ),
'id' => $asset_group_id,
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Edit asset group.
*
* @return callable
*/
public function edit_asset_group_callback(): callable {
return function ( Request $request ) {
try {
$asset_group_fields = array_intersect_key(
$request->get_params(),
$this->get_asset_group_fields()
);
if ( empty( $asset_group_fields ) && empty( $request->get_param( 'assets' ) ) ) {
throw new Exception( __( 'No asset group fields to update.', 'google-listings-and-ads' ) );
}
$asset_group_id = $this->ads_asset_group->edit_asset_group( $request->get_param( 'id' ), $asset_group_fields, $request->get_param( 'assets' ) );
return [
'status' => 'success',
'message' => __( 'Successfully edited asset group.', 'google-listings-and-ads' ),
'id' => $asset_group_id,
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'id' => [
'type' => 'number',
'description' => __( 'Asset Group ID', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'final_url' => [
'type' => 'string',
'description' => __( 'Final URL', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'display_url_path' => [
'type' => 'array',
'description' => __( 'Text that may appear appended to the url displayed in the ad.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'assets' => [
'type' => 'array',
'description' => __( 'Asset is a part of an ad which can be shared across multiple ads. It can be an image, headlines, descriptions, etc.', 'google-listings-and-ads' ),
'items' => [
'type' => 'object',
'properties' => [
AssetFieldType::SQUARE_MARKETING_IMAGE => $this->get_schema_field_type_asset(),
AssetFieldType::MARKETING_IMAGE => $this->get_schema_field_type_asset(),
AssetFieldType::PORTRAIT_MARKETING_IMAGE => $this->get_schema_field_type_asset(),
AssetFieldType::LOGO => $this->get_schema_field_type_asset(),
AssetFieldType::BUSINESS_NAME => $this->get_schema_field_type_asset(),
AssetFieldType::HEADLINE => $this->get_schema_field_type_asset(),
AssetFieldType::DESCRIPTION => $this->get_schema_field_type_asset(),
AssetFieldType::LONG_HEADLINE => $this->get_schema_field_type_asset(),
AssetFieldType::CALL_TO_ACTION_SELECTION => $this->get_schema_field_type_asset(),
],
],
],
];
}
/**
* Get the item schema for the field type asset.
*
* @return array the field type asset schema.
*/
protected function get_schema_field_type_asset(): array {
return [
'type' => 'array',
'items' => $this->get_schema_asset(),
'required' => false,
];
}
/**
* Get the item schema for the asset.
*
* @return array
*/
protected function get_schema_asset() {
return [
'type' => 'object',
'properties' => [
'id' => [
'type' => [ 'integer', 'null' ],
'description' => __( 'Asset ID', 'google-listings-and-ads' ),
],
'content' => [
'type' => [ 'string', 'null' ],
'description' => __( 'Asset content', 'google-listings-and-ads' ),
],
'field_type' => [
'type' => 'string',
'description' => __( 'Asset field type', 'google-listings-and-ads' ),
'required' => true,
'context' => [ 'edit' ],
'enum' => [
AssetFieldType::HEADLINE,
AssetFieldType::LONG_HEADLINE,
AssetFieldType::DESCRIPTION,
AssetFieldType::BUSINESS_NAME,
AssetFieldType::MARKETING_IMAGE,
AssetFieldType::SQUARE_MARKETING_IMAGE,
AssetFieldType::LOGO,
AssetFieldType::CALL_TO_ACTION_SELECTION,
AssetFieldType::PORTRAIT_MARKETING_IMAGE,
],
],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'asset-group';
}
}
Controllers/Ads/AssetSuggestionsController.php 0000644 00000014135 15155033414 0015655 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\Ads\AssetSuggestionsService;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class AssetSuggestionsController
*
* @since 2.4.0
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class AssetSuggestionsController extends BaseController {
/**
* Service used to populate ads suggestions data.
*
* @var AssetSuggestionsService
*/
protected $asset_suggestions_service;
/**
* AssetSuggestionsController constructor.
*
* @param RESTServer $server
* @param AssetSuggestionsService $asset_suggestions
*/
public function __construct( RESTServer $server, AssetSuggestionsService $asset_suggestions ) {
parent::__construct( $server );
$this->asset_suggestions_service = $asset_suggestions;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'assets/suggestions',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_assets_suggestions_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_assets_suggestions_params(),
],
]
);
$this->register_route(
'assets/final-url/suggestions',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_final_url_suggestions_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
return [
'search' => [
'description' => __( 'Search for post title or term name', 'google-listings-and-ads' ),
'type' => 'string',
'default' => '',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
],
'per_page' => [
'description' => __( 'The number of items to be return', 'google-listings-and-ads' ),
'type' => 'number',
'default' => 30,
'sanitize_callback' => 'absint',
'minimum' => 1,
'validate_callback' => 'rest_validate_request_arg',
],
'order_by' => [
'description' => __( 'Sort retrieved items by parameter', 'google-listings-and-ads' ),
'type' => 'string',
'default' => 'title',
'sanitize_callback' => 'sanitize_text_field',
'enum' => [ 'type', 'title', 'url' ],
'validate_callback' => 'rest_validate_request_arg',
],
];
}
/**
* Get the assets suggestions params.
*
* @return array
*/
public function get_assets_suggestions_params(): array {
return [
'id' => [
'description' => __( 'Post ID or Term ID.', 'google-listings-and-ads' ),
'type' => 'number',
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
'type' => [
'description' => __( 'Type linked to the id.', 'google-listings-and-ads' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'enum' => [ 'post', 'term', 'homepage' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
];
}
/**
* Get the callback function for the assets suggestions request.
*
* @return callable
*/
protected function get_assets_suggestions_callback(): callable {
return function ( Request $request ) {
try {
$id = $request->get_param( 'id' );
$type = $request->get_param( 'type' );
return $this->asset_suggestions_service->get_assets_suggestions( $id, $type );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for the list of final-url suggestions request.
*
* @return callable
*/
protected function get_final_url_suggestions_callback(): callable {
return function ( Request $request ) {
$search = $request->get_param( 'search' );
$per_page = $request->get_param( 'per_page' );
$order_by = $request->get_param( 'order_by' );
return array_map(
function ( $item ) use ( $request ) {
$data = $this->prepare_item_for_response( $item, $request );
return $this->prepare_response_for_collection( $data );
},
$this->asset_suggestions_service->get_final_url_suggestions( $search, $per_page, $order_by )
);
};
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'id' => [
'type' => 'number',
'description' => __( 'Post ID or Term ID', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'type' => [
'type' => 'string',
'description' => __( 'Post, term or homepage', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'enum' => [ 'post', 'term', 'homepage' ],
'readonly' => true,
],
'title' => [
'type' => 'string',
'description' => __( 'The post or term title', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'url' => [
'type' => 'string',
'description' => __( 'The URL linked to the post/term', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'asset_final_url_suggestions';
}
}
Controllers/Ads/BudgetRecommendationController.php 0000644 00000012206 15155033414 0016437 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\CountryCodeTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\BudgetRecommendationQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ISO3166AwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class BudgetRecommendationController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class BudgetRecommendationController extends BaseController implements ISO3166AwareInterface {
use CountryCodeTrait;
/**
* @var BudgetRecommendationQuery
*/
protected $budget_recommendation_query;
/**
* @var Ads
*/
protected $ads;
/**
* BudgetRecommendationController constructor.
*
* @param RESTServer $rest_server
* @param BudgetRecommendationQuery $budget_recommendation_query
* @param Ads $ads
*/
public function __construct( RESTServer $rest_server, BudgetRecommendationQuery $budget_recommendation_query, Ads $ads ) {
parent::__construct( $rest_server );
$this->budget_recommendation_query = $budget_recommendation_query;
$this->ads = $ads;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'ads/campaigns/budget-recommendation',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_budget_recommendation_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
'country_codes' => [
'type' => 'array',
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_country_code_validate_callback(),
'items' => [
'type' => 'string',
],
'required' => true,
'minItems' => 1,
],
];
}
/**
* @return callable
*/
protected function get_budget_recommendation_callback(): callable {
return function ( Request $request ) {
$country_codes = $request->get_param( 'country_codes' );
$currency = $this->ads->get_ads_currency();
if ( ! $currency ) {
return new Response(
[
'message' => __( 'No currency available for the Ads account.', 'google-listings-and-ads' ),
'currency' => $currency,
'country_codes' => $country_codes,
],
400
);
}
$recommendations = $this
->budget_recommendation_query
->where( 'country', $country_codes, 'IN' )
->where( 'currency', $currency )
->get_results();
if ( ! $recommendations ) {
return new Response(
[
'message' => __( 'Cannot find any budget recommendations.', 'google-listings-and-ads' ),
'currency' => $currency,
'country_codes' => $country_codes,
],
404
);
}
$returned_recommendations = array_map(
function ( $recommendation ) {
return [
'country' => $recommendation['country'],
'daily_budget' => (int) $recommendation['daily_budget'],
];
},
$recommendations
);
return $this->prepare_item_for_response(
[
'currency' => $currency,
'recommendations' => $returned_recommendations,
],
$request
);
};
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'currency' => [
'type' => 'string',
'description' => __( 'The currency to use for the shipping rate.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'validate_callback' => 'rest_validate_request_arg',
],
'recommendations' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'country' => [
'type' => 'string',
'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'daily_budget' => [
'type' => 'number',
'description' => __( 'The recommended daily budget for a country.', 'google-listings-and-ads' ),
],
],
],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'budget-recommendation';
}
}
Controllers/Ads/CampaignController.php 0000644 00000030775 15155033414 0014072 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\AdsCampaign;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\CampaignStatus;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\CampaignType;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\CountryCodeTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\GoogleHelperAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ISO3166AwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use DateTime;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class CampaignController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class CampaignController extends BaseController implements GoogleHelperAwareInterface, ISO3166AwareInterface {
use CountryCodeTrait;
/**
* @var AdsCampaign
*/
protected $ads_campaign;
/**
* CampaignController constructor.
*
* @param RESTServer $server
* @param AdsCampaign $ads_campaign
*/
public function __construct( RESTServer $server, AdsCampaign $ads_campaign ) {
parent::__construct( $server );
$this->ads_campaign = $ads_campaign;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'ads/campaigns',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_campaigns_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->create_campaign_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'ads/campaigns/(?P<id>[\d]+)',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_campaign_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::EDITABLE,
'callback' => $this->edit_campaign_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_edit_schema(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->delete_campaign_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for listing campaigns.
*
* @return callable
*/
protected function get_campaigns_callback(): callable {
return function ( Request $request ) {
try {
$exclude_removed = $request->get_param( 'exclude_removed' );
return array_map(
function ( $campaign ) use ( $request ) {
$data = $this->prepare_item_for_response( $campaign, $request );
return $this->prepare_response_for_collection( $data );
},
$this->ads_campaign->get_campaigns( $exclude_removed, true, $request->get_params() )
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for creating a campaign.
*
* @return callable
*/
protected function create_campaign_callback(): callable {
return function ( Request $request ) {
try {
$fields = array_intersect_key( $request->get_json_params(), $this->get_schema_properties() );
// Set the default value of campaign name.
if ( empty( $fields['name'] ) ) {
$current_date_time = ( new DateTime( 'now', wp_timezone() ) )->format( 'Y-m-d H:i:s' );
$fields['name'] = sprintf(
/* translators: %s: current date time. */
__( 'Campaign %s', 'google-listings-and-ads' ),
$current_date_time
);
}
$campaign = $this->ads_campaign->create_campaign( $fields );
/**
* When a campaign has been successfully created.
*
* @event gla_created_campaign
* @property int id Campaign ID.
* @property string status Campaign status, `enabled` or `paused`.
* @property string name Campaign name, generated based on date.
* @property float amount Campaign budget.
* @property string country Base target country code.
* @property string targeted_locations Additional target country codes.
* @property string source The source of the campaign creation.
*/
do_action(
'woocommerce_gla_track_event',
'created_campaign',
[
'id' => $campaign['id'],
'status' => $campaign['status'],
'name' => $campaign['name'],
'amount' => $campaign['amount'],
'country' => $campaign['country'],
'targeted_locations' => join( ',', $campaign['targeted_locations'] ),
'source' => $fields['label'] ?? '',
]
);
return $this->prepare_item_for_response( $campaign, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for listing a single campaign.
*
* @return callable
*/
protected function get_campaign_callback(): callable {
return function ( Request $request ) {
try {
$id = absint( $request['id'] );
$campaign = $this->ads_campaign->get_campaign( $id );
if ( empty( $campaign ) ) {
return new Response(
[
'message' => __( 'Campaign is not available.', 'google-listings-and-ads' ),
'id' => $id,
],
404
);
}
return $this->prepare_item_for_response( $campaign, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for editing a campaign.
*
* @return callable
*/
protected function edit_campaign_callback(): callable {
return function ( Request $request ) {
try {
$fields = array_intersect_key( $request->get_json_params(), $this->get_edit_schema() );
if ( empty( $fields ) ) {
return new Response(
[
'status' => 'invalid_data',
'message' => __( 'Invalid edit data.', 'google-listings-and-ads' ),
],
400
);
}
$campaign_id = $this->ads_campaign->edit_campaign( absint( $request['id'] ), $fields );
/**
* When a campaign has been successfully edited.
*
* @event gla_edited_campaign
* @property int id Campaign ID.
* @property string status Campaign status, `enabled` or `paused`.
* @property string name Campaign name, generated based on date.
* @property float amount Campaign budget.
*/
do_action(
'woocommerce_gla_track_event',
'edited_campaign',
array_merge(
[
'id' => $campaign_id,
],
$fields,
)
);
return [
'status' => 'success',
'message' => __( 'Successfully edited campaign.', 'google-listings-and-ads' ),
'id' => $campaign_id,
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for deleting a campaign.
*
* @return callable
*/
protected function delete_campaign_callback(): callable {
return function ( Request $request ) {
try {
$deleted_id = $this->ads_campaign->delete_campaign( absint( $request['id'] ) );
/**
* When a campaign has been successfully deleted.
*
* @event gla_deleted_campaign
* @property int id Campaign ID.
*/
do_action(
'woocommerce_gla_track_event',
'deleted_campaign',
[
'id' => $deleted_id,
]
);
return [
'status' => 'success',
'message' => __( 'Successfully deleted campaign.', 'google-listings-and-ads' ),
'id' => $deleted_id,
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the schema for fields we are allowed to edit.
*
* @return array
*/
protected function get_edit_schema(): array {
$allowed = [
'name',
'status',
'amount',
];
$fields = array_intersect_key( $this->get_schema_properties(), array_flip( $allowed ) );
// Unset required to allow editing individual fields.
array_walk(
$fields,
function ( &$value ) {
unset( $value['required'] );
}
);
return $fields;
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
return [
'exclude_removed' => [
'description' => __( 'Exclude removed campaigns.', 'google-listings-and-ads' ),
'type' => 'boolean',
'default' => true,
'validate_callback' => 'rest_validate_request_arg',
],
'per_page' => [
'description' => __( 'Maximum number of rows to be returned in result data.', 'google-listings-and-ads' ),
'type' => 'integer',
'minimum' => 1,
'maximum' => 10000,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
];
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'id' => [
'type' => 'integer',
'description' => __( 'ID number.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'name' => [
'type' => 'string',
'description' => __( 'Descriptive campaign name.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => false,
],
'status' => [
'type' => 'string',
'enum' => CampaignStatus::labels(),
'description' => __( 'Campaign status.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
],
'type' => [
'type' => 'string',
'enum' => CampaignType::labels(),
'description' => __( 'Campaign type.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
],
'amount' => [
'type' => 'number',
'description' => __( 'Daily budget amount in the local currency.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
'country' => [
'type' => 'string',
'description' => __( 'Country code of sale country in ISO 3166-1 alpha-2 format.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_supported_country_code_validate_callback(),
'readonly' => true,
],
'targeted_locations' => [
'type' => 'array',
'description' => __( 'The locations that an Ads campaign is targeting in ISO 3166-1 alpha-2 format.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_supported_country_code_validate_callback(),
'required' => true,
'minItems' => 1,
'items' => [
'type' => 'string',
],
],
'label' => [
'type' => 'string',
'description' => __( 'The name of the label to assign to the campaign.', 'google-listings-and-ads' ),
'context' => [ 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => false,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'campaign';
}
}
Controllers/Ads/ReportsController.php 0000644 00000015173 15155033414 0014004 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\AdsReport;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\CampaignStatus;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseReportsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Exception;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class ReportsController
*
* ContainerAware used for:
* - AdsReport
* - WP (in parent class)
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class ReportsController extends BaseReportsController {
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'ads/reports/programs',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_programs_report_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'ads/reports/products',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_products_report_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for the programs report request.
*
* @return callable
*/
protected function get_programs_report_callback(): callable {
return function ( Request $request ) {
try {
/** @var AdsReport $ads */
$ads = $this->container->get( AdsReport::class );
$data = $ads->get_report_data( 'campaigns', $this->prepare_query_arguments( $request ) );
return $this->prepare_item_for_response( $data, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for the products report request.
*
* @return callable
*/
protected function get_products_report_callback(): callable {
return function ( Request $request ) {
try {
/** @var AdsReport $ads */
$ads = $this->container->get( AdsReport::class );
$data = $ads->get_report_data( 'products', $this->prepare_query_arguments( $request ) );
return $this->prepare_item_for_response( $data, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
$params = parent::get_collection_params();
$params['interval'] = [
'description' => __( 'Time interval to use for segments in the returned data.', 'google-listings-and-ads' ),
'type' => 'string',
'enum' => [
'day',
'week',
'month',
'quarter',
'year',
],
'validate_callback' => 'rest_validate_request_arg',
];
return $params;
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'products' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'id' => [
'type' => 'string',
'description' => __( 'Product ID.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'name' => [
'type' => 'string',
'description' => __( 'Product name.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
],
'subtotals' => $this->get_totals_schema(),
],
],
],
'campaigns' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'id' => [
'type' => 'integer',
'description' => __( 'ID number.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'name' => [
'type' => 'string',
'description' => __( 'Campaign name.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
],
'status' => [
'type' => 'string',
'enum' => CampaignStatus::labels(),
'description' => __( 'Campaign status.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'isConverted' => [
'type' => 'boolean',
'description' => __( 'Whether the campaign has been converted', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'subtotals' => $this->get_totals_schema(),
],
],
],
'intervals' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'interval' => [
'type' => 'string',
'description' => __( 'ID of this report segment.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'subtotals' => $this->get_totals_schema(),
],
],
],
'totals' => $this->get_totals_schema(),
'next_page' => [
'type' => 'string',
'description' => __( 'Token to retrieve the next page of results.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
];
}
/**
* Return schema for total fields.
*
* @return array
*/
protected function get_totals_schema(): array {
return [
'type' => 'object',
'properties' => [
'clicks' => [
'type' => 'integer',
'description' => __( 'Clicks.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'impressions' => [
'type' => 'integer',
'description' => __( 'Impressions.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'sales' => [
'type' => 'number',
'description' => __( 'Sales amount.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'spend' => [
'type' => 'number',
'description' => __( 'Spend amount.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'conversions' => [
'type' => 'number',
'description' => __( 'Conversions.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'reports';
}
}
Controllers/Ads/SetupCompleteController.php 0000644 00000005045 15155033414 0015134 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\MerchantMetrics;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\EmptySchemaPropertiesTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class SetupCompleteController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class SetupCompleteController extends BaseController {
use EmptySchemaPropertiesTrait;
/**
* Service used to access metrics from the Ads Account.
*
* @var MerchantMetrics
*/
protected $metrics;
/**
* SetupCompleteController constructor.
*
* @param RESTServer $server
* @param MerchantMetrics $metrics
*/
public function __construct( RESTServer $server, MerchantMetrics $metrics ) {
parent::__construct( $server );
$this->metrics = $metrics;
}
/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
$this->register_route(
'ads/setup/complete',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_setup_complete_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback function for marking setup complete.
*
* @return callable
*/
protected function get_setup_complete_callback(): callable {
return function ( Request $request ) {
do_action( 'woocommerce_gla_ads_setup_completed' );
/**
* Ads onboarding has been successfully completed.
*
* @event gla_ads_setup_completed
* @property int campaign_count Number of campaigns for the connected Ads account.
*/
do_action(
'woocommerce_gla_track_event',
'ads_setup_completed',
[
'campaign_count' => $this->metrics->get_campaign_count(),
]
);
return new Response(
[
'status' => 'success',
'message' => __( 'Successfully marked Ads setup as completed.', 'google-listings-and-ads' ),
]
);
};
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'ads_setup_complete';
}
}
Controllers/AttributeMapping/AttributeMappingDataController.php 0000644 00000010200 15155033414 0021151 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\AttributeMapping;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\AttributeMappingHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class for handling API requests for getting source and destination data for Attribute Mapping
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\AttributeMapping
*/
class AttributeMappingDataController extends BaseOptionsController {
/**
* @var AttributeMappingHelper
*/
private AttributeMappingHelper $attribute_mapping_helper;
/**
* AttributeMappingDataController constructor.
*
* @param RESTServer $server
* @param AttributeMappingHelper $attribute_mapping_helper
*/
public function __construct( RESTServer $server, AttributeMappingHelper $attribute_mapping_helper ) {
parent::__construct( $server );
$this->attribute_mapping_helper = $attribute_mapping_helper;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
/**
* GET the destination fields for Google Shopping
*/
$this->register_route(
'mc/mapping/attributes',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_mapping_attributes_read_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
/**
* GET for getting the source data for a specific destination
*/
$this->register_route(
'mc/mapping/sources',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_mapping_sources_read_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => [
'attribute' => [
'description' => __( 'The attribute key to get the sources.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
],
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Callback function for returning the attributes
*
* @return callable
*/
protected function get_mapping_attributes_read_callback(): callable {
return function ( Request $request ) {
try {
return $this->prepare_item_for_response( $this->get_attributes(), $request );
} catch ( Exception $e ) {
return new Response( [ 'message' => $e->getMessage() ], $e->getCode() ?: 400 );
}
};
}
/**
* Callback function for returning the sources.
*
* @return callable
*/
protected function get_mapping_sources_read_callback(): callable {
return function ( Request $request ) {
try {
$attribute = $request->get_param( 'attribute' );
return [
'data' => $this->attribute_mapping_helper->get_sources_for_attribute( $attribute ),
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'data' => [
'type' => 'array',
'description' => __( 'The list of attributes or attribute sources.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'attribute_mapping_data';
}
/**
* Attributes getter
*
* @return array The attributes available for mapping
*/
private function get_attributes(): array {
return [
'data' => $this->attribute_mapping_helper->get_attributes(),
];
}
}
Controllers/AttributeMapping/AttributeMappingRulesController.php 0000644 00000021445 15155033414 0021407 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\AttributeMapping;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\AttributeMappingRulesQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\AttributeMappingHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_Error;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class for handling API requests for getting source and destination data for Attribute Mapping
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\AttributeMapping
*/
class AttributeMappingRulesController extends BaseOptionsController {
/**
* @var AttributeMappingRulesQuery
*/
private AttributeMappingRulesQuery $attribute_mapping_rules_query;
/**
* @var AttributeMappingHelper
*/
private AttributeMappingHelper $attribute_mapping_helper;
/**
* AttributeMappingRulesController constructor.
*
* @param RESTServer $server
* @param AttributeMappingHelper $attribute_mapping_helper
* @param AttributeMappingRulesQuery $attribute_mapping_rules_query
*/
public function __construct( RESTServer $server, AttributeMappingHelper $attribute_mapping_helper, AttributeMappingRulesQuery $attribute_mapping_rules_query ) {
parent::__construct( $server );
$this->attribute_mapping_helper = $attribute_mapping_helper;
$this->attribute_mapping_rules_query = $attribute_mapping_rules_query;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/mapping/rules',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_rule_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->create_rule_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
$this->register_route(
'mc/mapping/rules/(?P<id>[\d]+)',
[
[
'methods' => TransportMethods::EDITABLE,
'callback' => $this->update_rule_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->delete_rule_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Callback function for getting the Attribute Mapping rules from DB
*
* @return callable
*/
protected function get_rule_callback(): callable {
return function ( Request $request ) {
try {
$page = $request->get_param( 'page' );
$per_page = $request->get_param( 'per_page' );
$this->attribute_mapping_rules_query->set_limit( $per_page );
$this->attribute_mapping_rules_query->set_offset( $per_page * ( $page - 1 ) );
$rules = $this->attribute_mapping_rules_query->get_results();
$total_rules = $this->attribute_mapping_rules_query->get_count();
$response_data = [];
foreach ( $rules as $rule ) {
$item_data = $this->prepare_item_for_response( $rule, $request );
$response_data[] = $this->prepare_response_for_collection( $item_data );
}
return new Response(
$response_data,
200,
[
'X-WP-Total' => $total_rules,
'X-WP-TotalPages' => ceil( $total_rules / $per_page ),
]
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Callback function for saving an Attribute Mapping rule in DB
*
* @return callable
*/
protected function create_rule_callback(): callable {
return function ( Request $request ) {
try {
if ( ! $this->attribute_mapping_rules_query->insert( $this->prepare_item_for_database( $request ) ) ) {
return $this->response_from_exception( new Exception( 'Unable to create the new rule.' ) );
}
$response = $this->prepare_item_for_response( $this->attribute_mapping_rules_query->get_rule( $this->attribute_mapping_rules_query->last_insert_id() ), $request );
do_action( 'woocommerce_gla_mapping_rules_change' );
return $response;
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Callback function for saving an Attribute Mapping rule in DB
*
* @return callable
*/
protected function update_rule_callback(): callable {
return function ( Request $request ) {
try {
$rule_id = $request->get_url_params()['id'];
if ( ! $this->attribute_mapping_rules_query->update( $this->prepare_item_for_database( $request ), [ 'id' => $rule_id ] ) ) {
return $this->response_from_exception( new Exception( 'Unable to update the new rule.' ) );
}
$response = $this->prepare_item_for_response( $this->attribute_mapping_rules_query->get_rule( $rule_id ), $request );
do_action( 'woocommerce_gla_mapping_rules_change' );
return $response;
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Callback function for deleting an Attribute Mapping rule in DB
*
* @return callable
*/
protected function delete_rule_callback(): callable {
return function ( Request $request ) {
try {
$rule_id = $request->get_url_params()['id'];
if ( ! $this->attribute_mapping_rules_query->delete( 'id', $rule_id ) ) {
return $this->response_from_exception( new Exception( 'Unable to delete the rule' ) );
}
do_action( 'woocommerce_gla_mapping_rules_change' );
return [
'id' => $rule_id,
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema properties for the controller.
*
* @return array The Schema properties
*/
protected function get_schema_properties(): array {
return [
'id' => [
'description' => __( 'The Id for the rule.', 'google-listings-and-ads' ),
'type' => 'integer',
'validate_callback' => 'rest_validate_request_arg',
'readonly' => true,
],
'attribute' => [
'description' => __( 'The attribute value for the rule.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
'enum' => array_column( $this->attribute_mapping_helper->get_attributes(), 'id' ),
],
'source' => [
'description' => __( 'The source value for the rule.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
'category_condition_type' => [
'description' => __( 'The category condition type to apply for this rule.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
'enum' => $this->attribute_mapping_helper->get_category_condition_types(),
],
'categories' => [
'description' => __( 'List of category IDs, separated by commas.', 'google-listings-and-ads' ),
'type' => 'string',
'required' => false,
'validate_callback' => function ( $param ) {
return $this->validate_categories_param( $param );
},
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'attribute_mapping_rules';
}
/**
* @param string $categories Categories to validate
* @return bool|WP_Error True if it's validated
*
* @throw Exception when invalid categories are provided
*/
public function validate_categories_param( string $categories ) {
if ( $categories === '' ) {
return true;
}
$categories_array = explode( ',', $categories );
foreach ( $categories_array as $category ) {
if ( ! is_numeric( $category ) ) {
return new WP_Error(
'woocommerce_gla_attribute_mapping_invalid_categories_schema',
'categories should be a string of category IDs separated by commas.',
[
'categories' => $categories,
]
);
}
}
return true;
}
}
Controllers/AttributeMapping/AttributeMappingSyncerController.php 0000644 00000006417 15155033414 0021562 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\AttributeMapping;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ProductSyncStats;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class for handling API requests for getting the current Syncing state
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\AttributeMapping
*/
class AttributeMappingSyncerController extends BaseController implements OptionsAwareInterface {
use OptionsAwareTrait;
/**
* @var ProductSyncStats
*/
protected $sync_stats;
/**
* AttributeMappingSyncerController constructor.
*
* @param RESTServer $server
* @param ProductSyncStats $sync_stats
*/
public function __construct( RESTServer $server, ProductSyncStats $sync_stats ) {
parent::__construct( $server );
$this->sync_stats = $sync_stats;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/mapping/sync',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_sync_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Callback function for getting the Attribute Mapping Sync State
*
* @return callable
*/
protected function get_sync_callback(): callable {
return function ( Request $request ) {
try {
$state = [
'is_scheduled' => (bool) $this->sync_stats->get_count(),
'last_sync' => $this->options->get( OptionsInterface::UPDATE_ALL_PRODUCTS_LAST_SYNC ),
];
return $this->prepare_item_for_response( $state, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema properties for the controller.
*
* @return array The Schema properties
*/
protected function get_schema_properties(): array {
return [
'is_scheduled' => [
'description' => __( 'Indicates if the products are currently syncing', 'google-listings-and-ads' ),
'type' => 'boolean',
'validate_callback' => 'rest_validate_request_arg',
'readonly' => true,
'context' => [ 'view' ],
],
'last_sync' => [
'description' => __( 'Timestamp with the last sync.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'readonly' => true,
'context' => [ 'view' ],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'attribute_mapping_syncer';
}
}
Controllers/BaseController.php 0000644 00000012256 15155033414 0012510 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\API\PermissionsTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Registerable;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WC_REST_Controller;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
/**
* Class BaseEndpoint
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site
*/
abstract class BaseController extends WC_REST_Controller implements Registerable {
use PluginHelper;
use PermissionsTrait;
use ResponseFromExceptionTrait;
/**
* @var RESTServer
*/
protected $server;
/**
* BaseController constructor.
*
* @param RESTServer $server
*/
public function __construct( RESTServer $server ) {
$this->server = $server;
$this->namespace = $this->get_namespace();
}
/**
* Register a service.
*/
public function register(): void {
$this->register_routes();
}
/**
* Register a single route.
*
* @param string $route The route name.
* @param array $args The arguments for the route.
*/
protected function register_route( string $route, array $args ): void {
$this->server->register_route( $this->get_namespace(), $route, $args );
}
/**
* Get the namespace for the current controller.
*
* @return string
*/
protected function get_namespace(): string {
return "wc/{$this->get_slug()}";
}
/**
* Get the callback to determine the route's permissions.
*
* @return callable
*/
protected function get_permission_callback(): callable {
return function () {
return $this->can_manage();
};
}
/**
* Prepare an item schema for sending to the API.
*
* @param array $properties Array of raw properties.
* @param string $schema_title Schema title.
*
* @return array
*/
protected function prepare_item_schema( array $properties, string $schema_title ): array {
return $this->add_additional_fields_schema(
[
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $schema_title,
'type' => 'object',
'additionalProperties' => false,
'properties' => $properties,
]
);
}
/**
* Retrieves the item's schema, conforming to JSON Schema.
*
* @return array Item schema data.
*/
public function get_item_schema(): array {
return $this->prepare_item_schema( $this->get_schema_properties(), $this->get_schema_title() );
}
/**
* Get a callback function for returning the API schema.
*
* @return callable
*/
protected function get_api_response_schema_callback(): callable {
return function () {
return $this->get_item_schema();
};
}
/**
* Get a route name which is safe to use as a filter (removes namespace prefix).
*
* @param Request $request Request object.
*
* @return string
*/
protected function get_route_name( Request $request ): string {
$route = trim( $request->get_route(), '/' );
if ( 0 === strpos( $route, $this->get_namespace() ) ) {
$route = substr( $route, strlen( $this->get_namespace() ) );
}
return sanitize_title( $route );
}
/**
* Prepares the item for the REST response.
*
* @param mixed $item WordPress representation of the item.
* @param Request $request Request object.
*
* @return Response Response object on success, or WP_Error object on failure.
*/
public function prepare_item_for_response( $item, $request ) {
$prepared = [];
$context = $request['context'] ?? 'view';
$schema = $this->get_schema_properties();
foreach ( $schema as $key => $property ) {
$item_value = $item[ $key ] ?? $property['default'] ?? null;
// Cast empty arrays to empty objects if property is supposed to be an object.
if ( is_array( $item_value ) && empty( $item_value ) && isset( $property['type'] ) && 'object' === $property['type'] ) {
$item_value = (object) [];
}
$prepared[ $key ] = $item_value;
}
$prepared = $this->add_additional_fields_to_object( $prepared, $request );
$prepared = $this->filter_response_by_context( $prepared, $context );
$prepared = apply_filters(
'woocommerce_gla_prepared_response_' . $this->get_route_name( $request ),
$prepared,
$request
);
return new Response( $prepared );
}
/**
* Prepares one item for create or update operation.
*
* @param Request $request Request object.
*
* @return array The prepared item, or WP_Error object on failure.
*/
protected function prepare_item_for_database( $request ): array {
$prepared = [];
$schema = $this->get_schema_properties();
foreach ( $schema as $key => $property ) {
if ( $property['readonly'] ?? false ) {
continue;
}
$prepared[ $key ] = $request[ $key ] ?? $property['default'] ?? null;
}
return $prepared;
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
abstract protected function get_schema_properties(): array;
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
abstract protected function get_schema_title(): string;
}
Controllers/BaseOptionsController.php 0000644 00000001032 15155033414 0014052 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
defined( 'ABSPATH' ) || exit;
/**
* Class BaseOptionsController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
abstract class BaseOptionsController extends BaseController implements OptionsAwareInterface {
use OptionsAwareTrait;
}
Controllers/BaseReportsController.php 0000644 00000011177 15155033414 0014070 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\ContainerAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ContainerAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WP;
use DateTime;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class BaseReportsController
*
* ContainerAware used for:
* - WP
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
abstract class BaseReportsController extends BaseController implements ContainerAwareInterface {
use ContainerAwareTrait;
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
'after' => [
'description' => __( 'Limit response to data after a given ISO8601 compliant date.', 'google-listings-and-ads' ),
'type' => 'string',
'format' => 'date',
'default' => '-7 days',
'validate_callback' => 'rest_validate_request_arg',
],
'before' => [
'description' => __( 'Limit response to data before a given ISO8601 compliant date.', 'google-listings-and-ads' ),
'type' => 'string',
'format' => 'date',
'default' => 'now',
'validate_callback' => 'rest_validate_request_arg',
],
'ids' => [
'description' => __( 'Limit result to items with specified ids.', 'google-listings-and-ads' ),
'type' => 'array',
'sanitize_callback' => 'wp_parse_slug_list',
'validate_callback' => 'rest_validate_request_arg',
'items' => [
'type' => 'string',
],
],
'fields' => [
'description' => __( 'Limit totals to a set of fields.', 'google-listings-and-ads' ),
'type' => 'array',
'sanitize_callback' => 'wp_parse_slug_list',
'validate_callback' => 'rest_validate_request_arg',
'items' => [
'type' => 'string',
],
],
'order' => [
'description' => __( 'Order sort attribute ascending or descending.', 'google-listings-and-ads' ),
'type' => 'string',
'default' => 'desc',
'enum' => [ 'asc', 'desc' ],
'validate_callback' => 'rest_validate_request_arg',
],
'orderby' => [
'description' => __( 'Sort collection by attribute.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
],
'per_page' => [
'description' => __( 'Maximum number of rows to be returned in result data.', 'google-listings-and-ads' ),
'type' => 'integer',
'default' => 200,
'minimum' => 1,
'maximum' => 1000,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
'next_page' => [
'description' => __( 'Token to retrieve the next page.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
],
];
}
/**
* Maps query arguments from the REST request.
*
* @param Request $request REST Request.
* @return array
*/
protected function prepare_query_arguments( Request $request ): array {
$args = wp_parse_args(
array_intersect_key(
$request->get_query_params(),
$this->get_collection_params()
),
$request->get_default_params()
);
$this->normalize_timezones( $args );
return $args;
}
/**
* Converts input datetime parameters to local timezone.
*
* @param array $query_args Array of query arguments.
*/
protected function normalize_timezones( &$query_args ) {
/** @var WP $wp */
$wp = $this->container->get( WP::class );
$local_tz = $wp->wp_timezone();
foreach ( [ 'before', 'after' ] as $query_arg_key ) {
if ( isset( $query_args[ $query_arg_key ] ) && is_string( $query_args[ $query_arg_key ] ) ) {
// Assume that unspecified timezone is a local timezone.
$datetime = new DateTime( $query_args[ $query_arg_key ], $local_tz );
// In case timezone was forced by using +HH:MM, convert to local timezone.
$datetime->setTimezone( $local_tz );
$query_args[ $query_arg_key ] = $datetime;
} elseif ( isset( $query_args[ $query_arg_key ] ) && $query_args[ $query_arg_key ] instanceof DateTime ) {
// In case timezone is in other timezone, convert to local timezone.
$query_args[ $query_arg_key ]->setTimezone( $local_tz );
}
}
}
}
Controllers/BatchSchemaTrait.php 0000644 00000002640 15155033414 0012734 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
defined( 'ABSPATH' ) || exit;
/**
* Trait BatchSchemaTrait
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
trait BatchSchemaTrait {
use CountryCodeTrait;
/**
* Get the schema for a batch request.
*
* @return array
*/
public function get_item_schema(): array {
$schema = parent::get_schema_properties();
unset( $schema['country'], $schema['country_code'] );
// Context is always edit for batches.
foreach ( $schema as $key => &$value ) {
$value['context'] = [ 'edit' ];
}
$schema['country_codes'] = [
'type' => 'array',
'description' => __(
'Array of country codes in ISO 3166-1 alpha-2 format.',
'google-listings-and-ads'
),
'context' => [ 'edit' ],
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_country_code_validate_callback(),
'minItems' => 1,
'required' => true,
'uniqueItems' => true,
'items' => [
'type' => 'string',
],
];
return $schema;
}
/**
* Get the schema for a batch DELETE request.
*
* @return array
*/
public function get_item_delete_schema(): array {
$schema = $this->get_item_schema();
unset( $schema['rate'], $schema['currency'] );
return $schema;
}
}
Controllers/CountryCodeTrait.php 0000644 00000010052 15155033414 0013024 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\WPErrorTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\GoogleHelperAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\HelperTraits\ISO3166Awareness;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\League\ISO3166\Exception\OutOfBoundsException;
use WP_REST_Request as Request;
use Exception;
use Throwable;
defined( 'ABSPATH' ) || exit;
/**
* Trait CountryCodeTrait
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
trait CountryCodeTrait {
use GoogleHelperAwareTrait;
use ISO3166Awareness;
use WPErrorTrait;
/**
* Validate that a country is valid.
*
* @param string $country The alpha2 country code.
*
* @throws OutOfBoundsException When the country code cannot be found.
*/
protected function validate_country_code( string $country ): void {
$this->iso3166_data_provider->alpha2( $country );
}
/**
* Validate that a country or a list of countries is valid and supported,
* and also validate the data by the built-in validation of WP REST API with parameter’s schema.
*
* Since this extension's all API endpoints that use this validation function specify both
* `validate_callback` and `sanitize_callback`, this makes the built-in schema validation
* in WP REST API not to be applied. Therefore, this function calls `rest_validate_request_arg`
* first, so that the API endpoints can still benefit from the built-in schema validation.
*
* @param bool $check_supported_country Whether to check the country is supported.
* @param mixed $countries An individual string or an array of strings.
* @param Request $request The request to validate.
* @param string $param The parameter name, used in error messages.
*
* @return mixed
* @throws Exception When the country is not supported.
* @throws OutOfBoundsException When the country code cannot be found.
*/
protected function validate_country_codes( bool $check_supported_country, $countries, $request, $param ) {
$validation_result = rest_validate_request_arg( $countries, $request, $param );
if ( true !== $validation_result ) {
return $validation_result;
}
try {
// This is used for individual strings and an array of strings.
$countries = (array) $countries;
foreach ( $countries as $country ) {
$this->validate_country_code( $country );
if ( $check_supported_country ) {
$country_supported = $this->google_helper->is_country_supported( $country );
if ( ! $country_supported ) {
throw new Exception( __( 'Country is not supported', 'google-listings-and-ads' ) );
}
}
}
return true;
} catch ( Throwable $e ) {
return $this->error_from_exception(
$e,
'gla_invalid_country',
[
'status' => 400,
'country' => $countries,
]
);
}
}
/**
* Get the callback to sanitize the country code.
*
* Necessary because strtoupper() will trigger warnings when extra parameters are passed to it.
*
* @return callable
*/
protected function get_country_code_sanitize_callback(): callable {
return function ( $value ) {
return is_array( $value )
? array_map( 'strtoupper', $value )
: strtoupper( $value );
};
}
/**
* Get a callable function for validating that a provided country code is recognized
* and fulfilled the given parameter's schema.
*
* @return callable
*/
protected function get_country_code_validate_callback(): callable {
return function ( ...$args ) {
return $this->validate_country_codes( false, ...$args );
};
}
/**
* Get a callable function for validating that a provided country code is recognized, supported,
* and fulfilled the given parameter's schema..
*
* @return callable
*/
protected function get_supported_country_code_validate_callback(): callable {
return function ( ...$args ) {
return $this->validate_country_codes( true, ...$args );
};
}
}
Controllers/DisconnectController.php 0000644 00000004353 15155033414 0013726 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class DisconnectController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
class DisconnectController extends BaseController {
use EmptySchemaPropertiesTrait;
/**
* Register rest routes with WordPress.
*/
public function register_routes() {
$this->register_route(
'connections',
[
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->get_disconnect_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback for disconnecting all the services.
*
* @return callable
*/
protected function get_disconnect_callback(): callable {
return function ( Request $request ) {
$endpoints = [
'ads/connection',
'mc/connection',
'google/connect',
'jetpack/connect',
'rest-api/authorize',
];
$errors = [];
$responses = [];
foreach ( $endpoints as $endpoint ) {
$response = $this->get_delete_response( $endpoint );
if ( 200 !== $response->get_status() ) {
$errors[ $response->get_matched_route() ] = $response->get_data();
} else {
$responses[ $response->get_matched_route() ] = $response->get_data();
}
}
return new Response(
[
'errors' => $errors,
'responses' => $responses,
],
empty( $errors ) ? 200 : 400
);
};
}
/**
* Run a DELETE request for a given path, and return the response.
*
* @param string $path The relative API path. Based on the shared namespace.
*
* @return Response
*/
protected function get_delete_response( string $path ): Response {
$path = ltrim( $path, '/' );
return $this->server->dispatch_request( new Request( 'DELETE', "/{$this->get_namespace()}/{$path}" ) );
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'disconnect_all_accounts';
}
}
Controllers/EmptySchemaPropertiesTrait.php 0000644 00000000655 15155033414 0015072 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
/**
* Trait EmptySchemaPropertiesTrait
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
trait EmptySchemaPropertiesTrait {
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [];
}
}
Controllers/GTINMigrationController.php 0000644 00000005757 15155033414 0014261 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\HelperTraits\GTINMigrationUtilities;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\MigrateGTIN;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class GTINMigrationController offering API endpoint for GTIN field Migration
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
class GTINMigrationController extends BaseController {
use EmptySchemaPropertiesTrait;
use GTINMigrationUtilities;
/**
* Repository to fetch job responsible to run the migration in the background.
*
* @var JobRepository
*/
protected $job_repository;
/**
* Constructor.
*
* @param RESTServer $server
* @param JobRepository $job_repository
*/
public function __construct( RESTServer $server, JobRepository $job_repository ) {
parent::__construct( $server );
$this->job_repository = $job_repository;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'gtin-migration',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->start_migration_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_migration_status_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Callback function for scheduling GTIN migration job.
*
* @return callable
*/
protected function start_migration_callback(): callable {
return function () {
try {
$job = $this->job_repository->get( MigrateGTIN::class );
if ( ! $job->can_schedule( [ 1 ] ) ) {
return new Response(
[
'status' => 'error',
'message' => __( 'GTIN Migration cannot be scheduled.', 'google-listings-and-ads' ),
],
400
);
}
$job->schedule();
return new Response(
[
'status' => 'success',
'message' => __( 'GTIN Migration successfully started.', 'google-listings-and-ads' ),
],
200
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Callback function for getting the current migration status.
*
* @return callable
*/
protected function get_migration_status_callback(): callable {
return function () {
return new Response(
[
'status' => $this->get_gtin_migration_status(),
],
200
);
};
}
/**
* Get Schema title
*
* @return string
*/
protected function get_schema_title(): string {
return 'gtin_migration';
}
}
Controllers/Google/AccountController.php 0000644 00000014120 15155033414 0014436 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Google;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Connection;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class AccountController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Google
*/
class AccountController extends BaseController {
/**
* @var Connection
*/
protected $connection;
/**
* Mapping between the client page name and its path.
* The first value is also used as a default,
* and changing the order of keys/values may affect things below.
*
* @var string[]
*/
private const NEXT_PATH_MAPPING = [
'setup-mc' => '/google/setup-mc',
'setup-ads' => '/google/setup-ads',
'reconnect' => '/google/settings&subpath=/reconnect-google-account',
];
/**
* AccountController constructor.
*
* @param RESTServer $server
* @param Connection $connection
*/
public function __construct( RESTServer $server, Connection $connection ) {
parent::__construct( $server );
$this->connection = $connection;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'google/connect',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_connect_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_connect_params(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->get_disconnect_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'google/connected',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_connected_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
$this->register_route(
'google/reconnected',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_reconnected_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback function for the connection request.
*
* @return callable
*/
protected function get_connect_callback(): callable {
return function ( Request $request ) {
try {
$next = $request->get_param( 'next_page_name' );
$login_hint = $request->get_param( 'login_hint' ) ?: '';
$path = self::NEXT_PATH_MAPPING[ $next ];
return [
'url' => $this->connection->connect(
admin_url( "admin.php?page=wc-admin&path={$path}" ),
$login_hint
),
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the query params for the connection request.
*
* @return array
*/
protected function get_connect_params(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
'next_page_name' => [
'description' => __( 'Indicates the next page name mapped to the redirect URL when back from Google authorization.', 'google-listings-and-ads' ),
'type' => 'string',
'default' => array_key_first( self::NEXT_PATH_MAPPING ),
'enum' => array_keys( self::NEXT_PATH_MAPPING ),
'validate_callback' => 'rest_validate_request_arg',
],
'login_hint' => [
'description' => __( 'Indicate the Google account to suggest for authorization.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'is_email',
],
];
}
/**
* Get the callback function for the disconnection request.
*
* @return callable
*/
protected function get_disconnect_callback(): callable {
return function () {
$this->connection->disconnect();
return [
'status' => 'success',
'message' => __( 'Successfully disconnected.', 'google-listings-and-ads' ),
];
};
}
/**
* Get the callback function to determine if Google is currently connected.
*
* Uses consistent properties to the Jetpack connected callback
*
* @return callable
*/
protected function get_connected_callback(): callable {
return function () {
try {
$status = $this->connection->get_status();
return [
'active' => array_key_exists( 'status', $status ) && ( 'connected' === $status['status'] ) ? 'yes' : 'no',
'email' => array_key_exists( 'email', $status ) ? $status['email'] : '',
'scope' => array_key_exists( 'scope', $status ) ? $status['scope'] : [],
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function to determine if we have access to the dependent services.
*
* @return callable
*/
protected function get_reconnected_callback(): callable {
return function () {
try {
$status = $this->connection->get_reconnect_status();
$status['active'] = array_key_exists( 'status', $status ) && ( 'connected' === $status['status'] ) ? 'yes' : 'no';
unset( $status['status'] );
return $status;
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'url' => [
'type' => 'string',
'description' => __( 'The URL for making a connection to Google.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'google_account';
}
}
Controllers/Jetpack/AccountController.php 0000644 00000017412 15155033414 0014612 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Jetpack;
use Automattic\Jetpack\Connection\Manager;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Middleware;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class AccountController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Jetpack
*/
class AccountController extends BaseOptionsController {
/**
* @var Manager
*/
protected $manager;
/**
* @var Middleware
*/
protected $middleware;
/**
* Retain the connected state to prevent multiple external calls to validate the token.
*
* @var bool
*/
private $jetpack_connected_state;
/**
* Mapping between the client page name and its path.
* The first value is also used as a default,
* and changing the order of keys/values may affect things below.
*
* @var string[]
*/
private const NEXT_PATH_MAPPING = [
'setup-mc' => '/google/setup-mc',
'reconnect' => '/google/settings&subpath=/reconnect-wpcom-account',
];
/**
* AccountController constructor.
*
* @param RESTServer $server
* @param Manager $manager
* @param Middleware $middleware
*/
public function __construct( RESTServer $server, Manager $manager, Middleware $middleware ) {
parent::__construct( $server );
$this->manager = $manager;
$this->middleware = $middleware;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'jetpack/connect',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_connect_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_connect_params(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->get_disconnect_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'jetpack/connected',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_connected_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback function for the connection request.
*
* @return callable
*/
protected function get_connect_callback(): callable {
return function ( Request $request ) {
// Register the site to WPCOM.
if ( $this->manager->is_connected() ) {
$result = $this->manager->reconnect();
} else {
$result = $this->manager->register();
}
if ( is_wp_error( $result ) ) {
return new Response(
[
'status' => 'error',
'message' => $result->get_error_message(),
],
400
);
}
// Get an authorization URL which will redirect back to our page.
$next = $request->get_param( 'next_page_name' );
$path = self::NEXT_PATH_MAPPING[ $next ];
$redirect = admin_url( "admin.php?page=wc-admin&path={$path}" );
$auth_url = $this->manager->get_authorization_url( null, $redirect );
// Payments flow allows redirect back to the site without showing plans. Escaping the URL preventing XSS.
$auth_url = esc_url( add_query_arg( [ 'from' => 'google-listings-and-ads' ], $auth_url ), null, 'db' );
return [
'url' => $auth_url,
];
};
}
/**
* Get the query params for the connection request.
*
* @return array
*/
protected function get_connect_params(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
'next_page_name' => [
'description' => __( 'Indicates the next page name mapped to the redirect URL when back from Jetpack authorization.', 'google-listings-and-ads' ),
'type' => 'string',
'default' => array_key_first( self::NEXT_PATH_MAPPING ),
'enum' => array_keys( self::NEXT_PATH_MAPPING ),
'validate_callback' => 'rest_validate_request_arg',
],
];
}
/**
* Get the callback function for the disconnection request.
*
* @return callable
*/
protected function get_disconnect_callback(): callable {
return function () {
$this->manager->remove_connection();
$this->options->delete( OptionsInterface::WP_TOS_ACCEPTED );
$this->options->delete( OptionsInterface::JETPACK_CONNECTED );
return [
'status' => 'success',
'message' => __( 'Successfully disconnected.', 'google-listings-and-ads' ),
];
};
}
/**
* Get the callback function to determine if Jetpack is currently connected.
*
* @return callable
*/
protected function get_connected_callback(): callable {
return function () {
if ( $this->is_jetpack_connected() && ! $this->options->get( OptionsInterface::WP_TOS_ACCEPTED ) ) {
$this->log_wp_tos_accepted();
}
// Update connection status.
$this->options->update( OptionsInterface::JETPACK_CONNECTED, $this->is_jetpack_connected() );
$user_data = $this->get_jetpack_user_data();
return [
'active' => $this->display_boolean( $this->is_jetpack_connected() ),
'owner' => $this->display_boolean( $this->is_jetpack_connection_owner() ),
'displayName' => $user_data['display_name'] ?? '',
'email' => $user_data['email'] ?? '',
];
};
}
/**
* Determine whether Jetpack is connected.
* Check if manager is active and we have a valid token.
*
* @return bool
*/
protected function is_jetpack_connected(): bool {
if ( null !== $this->jetpack_connected_state ) {
return $this->jetpack_connected_state;
}
if ( ! $this->manager->has_connected_owner() || ! $this->manager->is_connected() ) {
$this->jetpack_connected_state = false;
return false;
}
// Send an external request to validate the token.
$this->jetpack_connected_state = $this->manager->get_tokens()->validate_blog_token();
return $this->jetpack_connected_state;
}
/**
* Determine whether user is the current Jetpack connection owner.
*
* @return bool
*/
protected function is_jetpack_connection_owner(): bool {
return $this->manager->is_connection_owner();
}
/**
* Format boolean for display.
*
* @param bool $value
*
* @return string
*/
protected function display_boolean( bool $value ): string {
return $value ? 'yes' : 'no';
}
/**
* Get the wpcom user data of the current connected user.
*
* @return array
*/
protected function get_jetpack_user_data(): array {
$user_data = $this->manager->get_connected_user_data();
// adjust for $user_data returning false
return is_array( $user_data ) ? $user_data : [];
}
/**
* Log accepted TOS for WordPress.
*/
protected function log_wp_tos_accepted() {
$user = wp_get_current_user();
$this->middleware->mark_tos_accepted( 'wp-com', $user->user_email );
$this->options->update( OptionsInterface::WP_TOS_ACCEPTED, true );
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'url' => [
'type' => 'string',
'description' => __( 'The URL for making a connection to Jetpack (wordpress.com).', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'jetpack_account';
}
}
Controllers/MerchantCenter/AccountController.php 0000644 00000017167 15155033414 0016142 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ApiNotReady;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\AccountService;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class AccountController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class AccountController extends BaseController {
/**
* Service used to access / update Ads account data.
*
* @var AccountService
*/
protected $account;
/**
* AccountController constructor.
*
* @param RESTServer $server
* @param AccountService $account
*/
public function __construct( RESTServer $server, AccountService $account ) {
parent::__construct( $server );
$this->account = $account;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/accounts',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_accounts_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->setup_account_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'mc/accounts/claim-overwrite',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->overwrite_claim_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'mc/accounts/switch-url',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->switch_url_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'mc/connection',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_connected_merchant_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->disconnect_merchant_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
$this->register_route(
'mc/setup',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_setup_merchant_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback function for the list accounts request.
*
* @return callable
*/
protected function get_accounts_callback(): callable {
return function ( Request $request ) {
try {
return array_map(
function ( $account ) use ( $request ) {
$data = $this->prepare_item_for_response( $account, $request );
return $this->prepare_response_for_collection( $data );
},
$this->account->get_accounts()
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback for creating or linking an account, overwriting the website claim during the claim step.
*
* @return callable
*/
protected function overwrite_claim_callback(): callable {
return $this->setup_account_callback( 'overwrite_claim' );
}
/**
* Get the callback for creating or linking an account, switching the URL during the set_id step.
*
* @return callable
*/
protected function switch_url_callback(): callable {
return $this->setup_account_callback( 'switch_url' );
}
/**
* Get the callback function for creating or linking an account.
*
* @param string $action Action to call while setting up account (default is normal setup).
* @return callable
*/
protected function setup_account_callback( string $action = 'setup_account' ): callable {
return function ( Request $request ) use ( $action ) {
try {
$account_id = absint( $request['id'] );
if ( $account_id && 'setup_account' === $action ) {
$this->account->use_existing_account_id( $account_id );
}
$account = $this->account->{$action}( $account_id );
return $this->prepare_item_for_response( $account, $request );
} catch ( ApiNotReady $e ) {
return $this->get_time_to_wait_response( $e );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for the connected merchant account.
*
* @return callable
*/
protected function get_connected_merchant_callback(): callable {
return function () {
return $this->account->get_connected_status();
};
}
/**
* Get the callback function for the merchant setup status.
*
* @return callable
*/
protected function get_setup_merchant_callback(): callable {
return function () {
return $this->account->get_setup_status();
};
}
/**
* Get the callback function for disconnecting a merchant.
*
* @return callable
*/
protected function disconnect_merchant_callback(): callable {
return function () {
$this->account->disconnect();
return [
'status' => 'success',
'message' => __( 'Merchant Center account successfully disconnected.', 'google-listings-and-ads' ),
];
};
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'id' => [
'type' => 'number',
'description' => __( 'Merchant Center Account ID.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => false,
],
'subaccount' => [
'type' => 'boolean',
'description' => __( 'Is a MCA sub account.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'name' => [
'type' => 'string',
'description' => __( 'The Merchant Center Account name.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'required' => false,
],
'domain' => [
'type' => 'string',
'description' => __( 'The domain registered with the Merchant Center Account.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'account';
}
/**
* Return a 503 Response with Retry-After header and message.
*
* @param ApiNotReady $wait Exception containing the time to wait.
*
* @return Response
*/
private function get_time_to_wait_response( ApiNotReady $wait ): Response {
$data = $wait->get_response_data( true );
return new Response(
$data,
$wait->getCode() ?: 503,
[
'Retry-After' => $data['retry_after'],
]
);
}
}
Controllers/MerchantCenter/AttributeMappingCategoriesController.php 0000644 00000006520 15155033414 0022022 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class for handling API requests for getting category tree in Attribute Mapping
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class AttributeMappingCategoriesController extends BaseController {
/**
* AttributeMappingCategoriesController constructor.
*
* @param RESTServer $server
*/
public function __construct( RESTServer $server ) {
parent::__construct( $server );
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/mapping/categories',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_categories_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Callback function for getting the category tree
*
* @return callable
*/
protected function get_categories_callback(): callable {
return function ( Request $request ) {
try {
$cats = $this->get_category_tree();
return array_map(
function ( $cats ) use ( $request ) {
$response = $this->prepare_item_for_response( $cats, $request );
return $this->prepare_response_for_collection( $response );
},
$cats
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema properties for the controller.
*
* @return array The Schema properties
*/
protected function get_schema_properties(): array {
return [
'id' => [
'description' => __( 'The Category ID.', 'google-listings-and-ads' ),
'type' => 'integer',
'validate_callback' => 'rest_validate_request_arg',
'readonly' => true,
],
'name' => [
'description' => __( 'The category name.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'readonly' => true,
],
'parent' => [
'description' => __( 'The category parent.', 'google-listings-and-ads' ),
'type' => 'integer',
'validate_callback' => 'rest_validate_request_arg',
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'attribute_mapping_categories';
}
/**
* Function to get all the categories
*
* @return array The categories
*/
private function get_category_tree(): array {
$categories = get_categories(
[
'taxonomy' => 'product_cat',
'hide_empty' => false,
]
);
return array_map(
function ( $category ) {
return [
'id' => $category->term_id,
'name' => $category->name,
'parent' => $category->parent,
];
},
$categories
);
}
}
Controllers/MerchantCenter/BatchShippingTrait.php 0000644 00000002273 15155033414 0016221 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Trait BatchShippingTrait
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
trait BatchShippingTrait {
/**
* Get the callback for deleting shipping items via batch.
*
* @return callable
*/
protected function get_batch_delete_shipping_callback(): callable {
return function ( Request $request ) {
$country_codes = $request->get_param( 'country_codes' );
$responses = [];
$errors = [];
foreach ( $country_codes as $country_code ) {
$route = "/{$this->get_namespace()}/{$this->route_base}/{$country_code}";
$delete_request = new Request( 'DELETE', $route );
$response = $this->server->dispatch_request( $delete_request );
if ( 200 !== $response->get_status() ) {
$errors[] = $response->get_data();
} else {
$responses[] = $response->get_data();
}
}
return new Response(
[
'errors' => $errors,
'success' => $responses,
],
);
};
}
}
Controllers/MerchantCenter/ConnectionController.php 0000644 00000003307 15155033414 0016634 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
defined( 'ABSPATH' ) || exit;
/**
* Class ConnectionController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ConnectionController extends BaseController {
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/connect',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_connect_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for the connection request.
*
* @return callable
*/
protected function get_connect_callback(): callable {
return function () {
return [
'url' => 'example.com',
];
};
}
/**
* Get the schema for settings endpoints.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'url' => [
'description' => __( 'Action that should be completed after connection.', 'google-listings-and-ads' ),
'type' => 'string',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'merchant_center_connection';
}
}
Controllers/MerchantCenter/ContactInformationController.php 0000644 00000023506 15155033414 0020341 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Settings;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\ContactInformation;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Utility\AddressUtility;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\PhoneNumber;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\ShoppingContent\AccountAddress;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\ShoppingContent\AccountBusinessInformation;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class ContactInformationController
*
* @since 1.4.0
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ContactInformationController extends BaseOptionsController {
/**
* @var ContactInformation $contact_information
*/
protected $contact_information;
/**
* @var Settings
*/
protected $settings;
/**
* @var AddressUtility
*/
protected $address_utility;
/**
* ContactInformationController constructor.
*
* @param RESTServer $server
* @param ContactInformation $contact_information
* @param Settings $settings
* @param AddressUtility $address_utility
*/
public function __construct( RESTServer $server, ContactInformation $contact_information, Settings $settings, AddressUtility $address_utility ) {
parent::__construct( $server );
$this->contact_information = $contact_information;
$this->settings = $settings;
$this->address_utility = $address_utility;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/contact-information',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_contact_information_endpoint_read_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_contact_information_endpoint_edit_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_update_args(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get a callback for the contact information endpoint.
*
* @return callable
*/
protected function get_contact_information_endpoint_read_callback(): callable {
return function ( Request $request ) {
try {
return $this->get_contact_information_response(
$this->contact_information->get_contact_information(),
$request
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get a callback for the edit contact information endpoint.
*
* @return callable
*/
protected function get_contact_information_endpoint_edit_callback(): callable {
return function ( Request $request ) {
try {
return $this->get_contact_information_response(
$this->contact_information->update_address_based_on_store_settings(),
$request
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the schema for contact information endpoints.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'id' => [
'type' => 'integer',
'description' => __( 'The Merchant Center account ID.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
],
'phone_number' => [
'type' => 'string',
'description' => __( 'The phone number associated with the Merchant Center account.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'phone_verification_status' => [
'type' => 'string',
'description' => __( 'The verification status of the phone number associated with the Merchant Center account.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'enum' => [ 'verified', 'unverified' ],
],
'mc_address' => [
'type' => 'object',
'description' => __( 'The address associated with the Merchant Center account.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'properties' => $this->get_address_schema(),
],
'wc_address' => [
'type' => 'object',
'description' => __( 'The WooCommerce store address.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'properties' => $this->get_address_schema(),
],
'is_mc_address_different' => [
'type' => 'boolean',
'description' => __( 'Whether the Merchant Center account address is different than the WooCommerce store address.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'wc_address_errors' => [
'type' => 'array',
'description' => __( 'The errors associated with the WooCommerce address', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
];
}
/**
* Get the schema for addresses returned by the contact information endpoints.
*
* @return array[]
*/
protected function get_address_schema(): array {
return [
'street_address' => [
'description' => __( 'Street-level part of the address.', 'google-listings-and-ads' ),
'type' => 'string',
'context' => [ 'view' ],
],
'locality' => [
'description' => __( 'City, town or commune. May also include dependent localities or sublocalities (e.g. neighborhoods or suburbs).', 'google-listings-and-ads' ),
'type' => 'string',
'context' => [ 'view' ],
],
'region' => [
'description' => __( 'Top-level administrative subdivision of the country. For example, a state like California ("CA") or a province like Quebec ("QC").', 'google-listings-and-ads' ),
'type' => 'string',
'context' => [ 'view' ],
],
'postal_code' => [
'description' => __( 'Postal code or ZIP (e.g. "94043").', 'google-listings-and-ads' ),
'type' => 'string',
'context' => [ 'view' ],
],
'country' => [
'description' => __( 'CLDR country code (e.g. "US").', 'google-listings-and-ads' ),
'type' => 'string',
'context' => [ 'view' ],
],
];
}
/**
* Get the arguments for the update endpoint.
*
* @return array
*/
public function get_update_args(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
];
}
/**
* Get the prepared REST response with Merchant Center account ID and contact information.
*
* @param AccountBusinessInformation|null $contact_information
* @param Request $request
*
* @return Response
*/
protected function get_contact_information_response( ?AccountBusinessInformation $contact_information, Request $request ): Response {
$phone_number = null;
$phone_verification_status = null;
$mc_address = null;
$wc_address = null;
$is_address_diff = false;
if ( $this->settings->get_store_address() instanceof AccountAddress ) {
$wc_address = $this->settings->get_store_address();
$is_address_diff = true;
}
if ( $contact_information instanceof AccountBusinessInformation ) {
if ( ! empty( $contact_information->getPhoneNumber() ) ) {
try {
$phone_number = PhoneNumber::cast( $contact_information->getPhoneNumber() )->get();
$phone_verification_status = strtolower( $contact_information->getPhoneVerificationStatus() );
} catch ( InvalidValue $exception ) {
// log and fail silently
do_action( 'woocommerce_gla_exception', $exception, __METHOD__ );
}
}
if ( $contact_information->getAddress() instanceof AccountAddress ) {
$mc_address = $contact_information->getAddress();
$is_address_diff = true;
}
if ( null !== $mc_address && null !== $wc_address ) {
$is_address_diff = ! $this->address_utility->compare_addresses( $contact_information->getAddress(), $this->settings->get_store_address() );
}
}
$wc_address_errors = $this->settings->wc_address_errors( $wc_address );
return $this->prepare_item_for_response(
[
'id' => $this->options->get_merchant_id(),
'phone_number' => $phone_number,
'phone_verification_status' => $phone_verification_status,
'mc_address' => self::serialize_address( $mc_address ),
'wc_address' => self::serialize_address( $wc_address ),
'is_mc_address_different' => $is_address_diff,
'wc_address_errors' => $wc_address_errors,
],
$request
);
}
/**
* @param AccountAddress|null $address
*
* @return array|null
*/
protected static function serialize_address( ?AccountAddress $address ): ?array {
if ( null === $address ) {
return null;
}
return [
'street_address' => $address->getStreetAddress(),
'locality' => $address->getLocality(),
'region' => $address->getRegion(),
'postal_code' => $address->getPostalCode(),
'country' => $address->getCountry(),
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'merchant_center_contact_information';
}
}
Controllers/MerchantCenter/IssuesController.php 0000644 00000015561 15155033414 0016015 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantStatuses;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class IssuesController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class IssuesController extends BaseOptionsController {
/**
* @var MerchantStatuses
*/
protected $merchant_statuses;
/**
* @var ProductHelper
*/
protected $product_helper;
/**
* IssuesController constructor.
*
* @param RESTServer $server
* @param MerchantStatuses $merchant_statuses
* @param ProductHelper $product_helper
*/
public function __construct( RESTServer $server, MerchantStatuses $merchant_statuses, ProductHelper $product_helper ) {
parent::__construct( $server );
$this->merchant_statuses = $merchant_statuses;
$this->product_helper = $product_helper;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/issues(/(?P<type_filter>[a-z]+))?',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_issues_read_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Get the callback function for returning account and product issues.
*
* @return callable
*/
protected function get_issues_read_callback(): callable {
return function ( Request $request ) {
$type_filter = $request['type_filter'];
$per_page = intval( $request['per_page'] );
$page = max( 1, intval( $request['page'] ) );
try {
$results = $this->merchant_statuses->get_issues( $type_filter, $per_page, $page );
$results['page'] = $page;
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
// Replace variation IDs with parent ID (for Edit links).
foreach ( $results['issues'] as &$issue ) {
$issue = apply_filters( 'woocommerce_gla_merchant_issue_override', $issue );
if ( empty( $issue['product_id'] ) ) {
continue;
}
try {
$issue['product_id'] = $this->product_helper->maybe_swap_for_parent_id( $issue['product_id'] );
} catch ( InvalidValue $e ) {
// Don't include invalid products
do_action(
'woocommerce_gla_debug_message',
sprintf( 'Merchant Center product ID %s not found in this WooCommerce store.', $issue['product_id'] ),
__METHOD__,
);
continue;
}
}
return $this->prepare_item_for_response( $results, $request );
};
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'issues' => [
'type' => 'array',
'description' => __( 'The issues related to the Merchant Center account.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
'items' => [
'type' => 'object',
'properties' => [
'type' => [
'type' => 'string',
'description' => __( 'Issue type.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'product' => [
'type' => 'string',
'description' => __( 'Affected product.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'product_id' => [
'type' => 'numeric',
'description' => __( 'The WooCommerce product ID.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'code' => [
'type' => 'string',
'description' => __( 'Internal Google code for issue.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'issue' => [
'type' => 'string',
'description' => __( 'Descriptive text of the issue.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'action' => [
'type' => 'string',
'description' => __( 'Descriptive text of action to take.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'action_url' => [
'type' => 'string',
'description' => __( 'Documentation URL for issue and/or action.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'severity' => [
'type' => 'string',
'description' => __( 'Severity level of the issue: warning or error.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'applicable_countries' => [
'type' => 'array',
'description' => __( 'Country codes of the product audience.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
],
],
],
'total' => [
'type' => 'numeric',
'context' => [ 'view' ],
'readonly' => true,
],
'page' => [
'type' => 'numeric',
'context' => [ 'view' ],
'readonly' => true,
],
'loading' => [
'type' => 'boolean',
'description' => __( 'Whether the product issues are loading.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
];
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
'page' => [
'description' => __( 'Page of data to retrieve.', 'google-listings-and-ads' ),
'type' => 'integer',
'default' => 1,
'minimum' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
'per_page' => [
'description' => __( 'Maximum number of rows to be returned in result data.', 'google-listings-and-ads' ),
'type' => 'integer',
'default' => 0,
'minimum' => 0,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'merchant_issues';
}
}
Controllers/MerchantCenter/PhoneVerificationController.php 0000644 00000012051 15155033414 0020145 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\EmptySchemaPropertiesTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\PhoneVerification;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\PhoneNumber;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class PhoneVerificationController
*
* @since 1.5.0
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class PhoneVerificationController extends BaseOptionsController {
use EmptySchemaPropertiesTrait;
/**
* @var PhoneVerification
*/
protected $phone_verification;
/**
* PhoneVerificationController constructor.
*
* @param RESTServer $server
* @param PhoneVerification $phone_verification Phone verification service.
*/
public function __construct( RESTServer $server, PhoneVerification $phone_verification ) {
parent::__construct( $server );
$this->phone_verification = $phone_verification;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$verification_method = [
'description' => __( 'Method used to verify the phone number.', 'google-listings-and-ads' ),
'enum' => [
PhoneVerification::VERIFICATION_METHOD_SMS,
PhoneVerification::VERIFICATION_METHOD_PHONE_CALL,
],
'required' => true,
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
];
$this->register_route(
'/mc/phone-verification/request',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_request_phone_verification_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => [
'phone_region_code' => [
'description' => __( 'Two-letter country code (ISO 3166-1 alpha-2) for the phone number.', 'google-listings-and-ads' ),
'required' => true,
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
],
'phone_number' => [
'description' => __( 'The phone number to verify.', 'google-listings-and-ads' ),
'required' => true,
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
],
'verification_method' => $verification_method,
],
],
]
);
$this->register_route(
'/mc/phone-verification/verify',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_verify_phone_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => [
'verification_id' => [
'description' => __( 'The verification ID returned by the /request call.', 'google-listings-and-ads' ),
'required' => true,
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
],
'verification_code' => [
'description' => __( 'The verification code that was sent to the phone number for validation.', 'google-listings-and-ads' ),
'required' => true,
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
],
'verification_method' => $verification_method,
],
],
]
);
}
/**
* Get callback for requesting phone verification endpoint.
*
* @return callable
*/
protected function get_request_phone_verification_callback(): callable {
return function ( Request $request ) {
try {
$verification_id = $this->phone_verification->request_phone_verification(
$request->get_param( 'phone_region_code' ),
new PhoneNumber( $request->get_param( 'phone_number' ) ),
$request->get_param( 'verification_method' ),
);
return [
'verification_id' => $verification_id,
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get callback for verifying a phone number.
*
* @return callable
*/
protected function get_verify_phone_callback(): callable {
return function ( Request $request ) {
try {
$this->phone_verification->verify_phone_number(
$request->get_param( 'verification_id' ),
$request->get_param( 'verification_code' ),
$request->get_param( 'verification_method' ),
);
return new Response( null, 204 );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema name for the controller.
*
* @return string
*/
protected function get_schema_title(): string {
return 'phone_verification';
}
}
Controllers/MerchantCenter/PolicyComplianceCheckController.php 0000644 00000011104 15155033414 0020717 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\CountryCodeTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\PolicyComplianceCheck;
use Exception;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class PolicyComplianceCheckController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class PolicyComplianceCheckController extends BaseController {
use CountryCodeTrait;
/**
* The PolicyComplianceCheck object.
*
* @var PolicyComplianceCheck
*/
protected $policy_compliance_check;
/**
* PolicyComplianceCheckController constructor.
*
* @param RESTServer $server
* @param PolicyComplianceCheck $policy_compliance_check
*/
public function __construct( RESTServer $server, PolicyComplianceCheck $policy_compliance_check ) {
parent::__construct( $server );
$this->policy_compliance_check = $policy_compliance_check;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/policy_check',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_policy_check_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the allowed countries, payment gateways info, store ssl and refund return policy page for the controller.
*
* @return callable
*/
protected function get_policy_check_callback(): callable {
return function () {
try {
return new Response(
[
'allowed_countries' => $this->policy_compliance_check->is_accessible(),
'robots_restriction' => $this->policy_compliance_check->has_restriction(),
'page_not_found_error' => $this->policy_compliance_check->has_page_not_found_error(),
'page_redirects' => $this->policy_compliance_check->has_redirects(),
'payment_gateways' => $this->policy_compliance_check->has_payment_gateways(),
'store_ssl' => $this->policy_compliance_check->get_is_store_ssl(),
'refund_returns' => $this->policy_compliance_check->has_refund_return_policy_page(),
]
);
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the schema for policy compliance check endpoints.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'allowed_countries' => [
'type' => 'boolean',
'description' => __( 'The store website could be accessed or not by all users.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'robots_restriction' => [
'type' => 'boolean',
'description' => __( 'The merchant set the restrictions in robots.txt or not in the store.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'page_not_found_error' => [
'type' => 'boolean',
'description' => __( 'The sample of product landing pages leads to a 404 error.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'page_redirects' => [
'type' => 'boolean',
'description' => __( 'The sample of product landing pages have redirects through 3P domains.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'payment_gateways' => [
'type' => 'boolean',
'description' => __( 'The payment gateways associated with onboarding policy checking.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'store_ssl' => [
'type' => 'boolean',
'description' => __( 'The store ssl associated with onboarding policy checking.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'refund_returns' => [
'type' => 'boolean',
'description' => __( 'The refund returns policy associated with onboarding policy checking.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'schema' => $this->get_api_response_schema_callback(),
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'policy_check';
}
}
Controllers/MerchantCenter/ProductFeedController.php 0000644 00000014342 15155033414 0016742 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\ProductFeedQueryHelper;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
defined( 'ABSPATH' ) || exit;
/**
* Class ProductFeedController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ProductFeedController extends BaseController {
/**
* @var ProductFeedQueryHelper
*/
protected $query_helper;
/**
* ProductFeedController constructor.
*
* @param RESTServer $server
* @param ProductFeedQueryHelper $query_helper
*/
public function __construct( RESTServer $server, ProductFeedQueryHelper $query_helper ) {
parent::__construct( $server );
$this->query_helper = $query_helper;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/product-feed',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_product_feed_read_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Get the callback function for returning the product feed.
*
* @return callable
*/
protected function get_product_feed_read_callback(): callable {
return function ( Request $request ) {
try {
return [
'products' => $this->query_helper->get( $request ),
'total' => $this->query_helper->count( $request ),
'page' => $request['per_page'] > 0 && $request['page'] > 0 ? $request['page'] : 1,
];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'products' => [
'type' => 'array',
'description' => __( 'The store\'s products.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
'items' => [
'type' => 'object',
'properties' => [
'id' => [
'type' => 'numeric',
'description' => __( 'Product ID.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'title' => [
'type' => 'string',
'description' => __( 'Product title.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'visible' => [
'type' => 'boolean',
'description' => __( 'Whether the product is set to be visible in the Merchant Center', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'status' => [
'type' => 'string',
'description' => __( 'The current sync status of the product.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'image_url' => [
'type' => 'string',
'description' => __( 'The image url of the product.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'price' => [
'type' => 'string',
'description' => __( 'The price of the product.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'errors' => [
'type' => 'array',
'description' => __( 'Errors preventing the product from being synced to the Merchant Center.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
],
],
],
'total' => [
'type' => 'numeric',
'context' => [ 'view' ],
'readonly' => true,
],
'page' => [
'type' => 'numeric',
'context' => [ 'view' ],
'readonly' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'product_feed';
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
'page' => [
'description' => __( 'Page of data to retrieve.', 'google-listings-and-ads' ),
'type' => 'integer',
'default' => 1,
'minimum' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
'per_page' => [
'description' => __( 'Maximum number of rows to be returned in result data.', 'google-listings-and-ads' ),
'type' => 'integer',
'default' => 0,
'minimum' => 0,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
'search' => [
'description' => __( 'Text to search for in product names.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
],
'ids' => [
'description' => __( 'Limit result to items with specified ids (comma-separated).', 'google-listings-and-ads' ),
'type' => 'array',
'sanitize_callback' => 'wp_parse_list',
'validate_callback' => 'rest_validate_request_arg',
'items' => [
'type' => 'integer',
],
],
'orderby' => [
'description' => __( 'Sort collection by attribute.', 'google-listings-and-ads' ),
'type' => 'string',
'default' => 'title',
'enum' => [ 'title', 'id', 'visible', 'status' ],
'validate_callback' => 'rest_validate_request_arg',
],
'order' => [
'description' => __( 'Order sort attribute ascending or descending.', 'google-listings-and-ads' ),
'type' => 'string',
'default' => 'ASC',
'enum' => [ 'ASC', 'DESC' ],
'validate_callback' => 'rest_validate_request_arg',
],
];
}
}
Controllers/MerchantCenter/ProductStatisticsController.php 0000644 00000013475 15155033414 0020237 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ProductSyncStats;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantStatuses;
use WP_REST_Response as Response;
use WP_REST_Request as Request;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class ProductStatisticsController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ProductStatisticsController extends BaseOptionsController {
/**
* The MerchantProducts object.
*
* @var MerchantStatuses
*/
protected $merchant_statuses;
/**
* Helper class to count scheduled sync jobs.
*
* @var ProductSyncStats
*/
protected $sync_stats;
/**
* ProductStatisticsController constructor.
*
* @param RESTServer $server
* @param MerchantStatuses $merchant_statuses
* @param ProductSyncStats $sync_stats
*/
public function __construct( RESTServer $server, MerchantStatuses $merchant_statuses, ProductSyncStats $sync_stats ) {
parent::__construct( $server );
$this->merchant_statuses = $merchant_statuses;
$this->sync_stats = $sync_stats;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/product-statistics',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_product_statistics_read_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
$this->register_route(
'mc/product-statistics/refresh',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_product_statistics_refresh_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Get the callback function for returning product statistics.
*
* @return callable
*/
protected function get_product_statistics_read_callback(): callable {
return function ( Request $request ) {
return $this->get_product_status_stats( $request );
};
}
/**
* Get the callback function for getting re-calculated product statistics.
*
* @return callable
*/
protected function get_product_statistics_refresh_callback(): callable {
return function ( Request $request ) {
return $this->get_product_status_stats( $request, true );
};
}
/**
* Get the overall product status statistics array.
*
* @param Request $request
* @param bool $force_refresh True to force a refresh of the product status statistics.
*
* @return Response
*/
protected function get_product_status_stats( Request $request, bool $force_refresh = false ): Response {
try {
$response = $this->merchant_statuses->get_product_statistics( $force_refresh );
$response['scheduled_sync'] = $this->sync_stats->get_count();
return $this->prepare_item_for_response( $response, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'timestamp' => [
'type' => 'number',
'description' => __( 'Timestamp reflecting when the product status statistics were last generated.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'statistics' => [
'type' => 'object',
'description' => __( 'Merchant Center product status statistics.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
'properties' => [
'active' => [
'type' => 'integer',
'description' => __( 'Active products.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'expiring' => [
'type' => 'integer',
'description' => __( 'Expiring products.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'pending' => [
'type' => 'number',
'description' => __( 'Pending products.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'disapproved' => [
'type' => 'number',
'description' => __( 'Disapproved products.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'not_synced' => [
'type' => 'number',
'description' => __( 'Products not uploaded.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
],
],
'scheduled_sync' => [
'type' => 'number',
'description' => __( 'Amount of scheduled jobs which will sync products to Google.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'loading' => [
'type' => 'boolean',
'description' => __( 'Whether the product statistics are loading.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'error' => [
'type' => 'string',
'description' => __( 'Error message in case of failure', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
'default' => null,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'product_statistics';
}
}
Controllers/MerchantCenter/ProductVisibilityController.php 0000644 00000013050 15155033414 0020221 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\MerchantIssueQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductMetaHandler;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\ChannelVisibility;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class ProductVisibilityController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ProductVisibilityController extends BaseController {
use PluginHelper;
/**
* @var ProductHelper $product_helper
*/
protected $product_helper;
/**
* @var MerchantIssueQuery $issue_query
*/
protected $issue_query;
/**
* ProductVisibilityController constructor.
*
* @param RESTServer $server
* @param ProductHelper $product_helper
* @param MerchantIssueQuery $issue_query
*/
public function __construct( RESTServer $server, ProductHelper $product_helper, MerchantIssueQuery $issue_query ) {
parent::__construct( $server );
$this->product_helper = $product_helper;
$this->issue_query = $issue_query;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/product-visibility',
[
[
'methods' => TransportMethods::EDITABLE,
'callback' => $this->get_update_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_update_args(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get a callback for updating products' channel visibility.
*
* @return callable
*/
protected function get_update_callback(): callable {
return function ( Request $request ) {
$ids = $request->get_param( 'ids' );
$visible = $request->get_param( 'visible' );
$success = [];
$errors = [];
foreach ( $ids as $product_id ) {
$product_id = intval( $product_id );
if ( ! $this->change_product_visibility( $product_id, $visible ) ) {
$errors[] = $product_id;
continue;
}
if ( ! $visible ) {
$this->issue_query->delete( 'product_id', $product_id );
}
$success[] = $product_id;
}
sort( $success );
sort( $errors );
return new Response(
[
'success' => $success,
'errors' => $errors,
],
count( $errors ) ? 400 : 200
);
};
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'success' => [
'type' => 'array',
'description' => __( 'Products whose visibility was changed successfully.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'validate_callback' => 'rest_validate_request_arg',
'items' => [
'type' => 'numeric',
],
],
'errors' => [
'type' => 'array',
'description' => __( 'Products whose visibility was not changed.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'validate_callback' => 'rest_validate_request_arg',
'items' => [
'type' => 'numeric',
],
],
];
}
/**
* Get the arguments for the update endpoint.
*
* @return array
*/
public function get_update_args(): array {
return [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
'ids' => [
'description' => __( 'IDs of the products to update.', 'google-listings-and-ads' ),
'type' => 'array',
'sanitize_callback' => 'wp_parse_slug_list',
'validate_callback' => 'rest_validate_request_arg',
'items' => [
'type' => 'integer',
],
],
'visible' => [
'description' => __( 'New Visibility status for the specified products.', 'google-listings-and-ads' ),
'type' => 'boolean',
'validate_callback' => 'rest_validate_request_arg',
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'product_visibility';
}
/**
* Update a product's Merchant Center visibility setting (or parent product, for variations).
*
* @param int $product_id
* @param bool $new_visibility True for visible, false for not visible.
*
* @return bool True if the product was found and updated correctly.
*/
protected function change_product_visibility( int $product_id, bool $new_visibility ): bool {
try {
$product = $this->product_helper->get_wc_product( $product_id );
$product = $this->product_helper->maybe_swap_for_parent( $product );
// Use $product->save() instead of ProductMetaHandler to trigger MC sync.
$product->update_meta_data(
$this->prefix_meta_key( ProductMetaHandler::KEY_VISIBILITY ),
$new_visibility ? ChannelVisibility::SYNC_AND_SHOW : ChannelVisibility::DONT_SYNC_AND_SHOW
);
$product->save();
return true;
} catch ( Exception $e ) {
do_action( 'woocommerce_gla_exception', $e, __METHOD__ );
return false;
}
}
}
Controllers/MerchantCenter/ReportsController.php 0000644 00000012522 15155033414 0016172 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\MerchantReport;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseReportsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Exception;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class ReportsController
*
* ContainerAware used for:
* - MerchantReport
* - WP (in parent class)
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ReportsController extends BaseReportsController {
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/reports/programs',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_programs_report_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'mc/reports/products',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_products_report_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_collection_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for the programs report request.
*
* @return callable
*/
protected function get_programs_report_callback(): callable {
return function ( Request $request ) {
try {
/** @var MerchantReport $merchant */
$merchant = $this->container->get( MerchantReport::class );
$data = $merchant->get_report_data( 'free_listings', $this->prepare_query_arguments( $request ) );
return $this->prepare_item_for_response( $data, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for the products report request.
*
* @return callable
*/
protected function get_products_report_callback(): callable {
return function ( Request $request ) {
try {
/** @var MerchantReport $merchant */
$merchant = $this->container->get( MerchantReport::class );
$data = $merchant->get_report_data( 'products', $this->prepare_query_arguments( $request ) );
return $this->prepare_item_for_response( $data, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params(): array {
$params = parent::get_collection_params();
$params['interval'] = [
'description' => __( 'Time interval to use for segments in the returned data.', 'google-listings-and-ads' ),
'type' => 'string',
'enum' => [
'day',
],
'validate_callback' => 'rest_validate_request_arg',
];
return $params;
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'free_listings' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'subtotals' => $this->get_totals_schema(),
],
],
],
'products' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'id' => [
'type' => 'string',
'description' => __( 'Product ID.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'name' => [
'type' => 'string',
'description' => __( 'Product name.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
],
'subtotals' => $this->get_totals_schema(),
],
],
],
'intervals' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'interval' => [
'type' => 'string',
'description' => __( 'ID of this report segment.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'subtotals' => $this->get_totals_schema(),
],
],
],
'totals' => $this->get_totals_schema(),
'next_page' => [
'type' => 'string',
'description' => __( 'Token to retrieve the next page of results.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
];
}
/**
* Return schema for total fields.
*
* @return array
*/
protected function get_totals_schema(): array {
return [
'type' => 'object',
'properties' => [
'clicks' => [
'type' => 'integer',
'description' => __( 'Clicks.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'impressions' => [
'type' => 'integer',
'description' => __( 'Impressions.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'reports';
}
}
Controllers/MerchantCenter/RequestReviewController.php 0000644 00000024531 15155033414 0017351 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Merchant;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Middleware;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\RequestReviewStatuses;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\TransientsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class RequestReviewController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class RequestReviewController extends BaseOptionsController {
/**
* @var TransientsInterface
*/
private $transients;
/**
* RequestReviewController constructor.
*
* @param RESTServer $server
* @param Middleware $middleware
* @param Merchant $merchant
* @param RequestReviewStatuses $request_review_statuses
* @param TransientsInterface $transients
*/
public function __construct( RESTServer $server, Middleware $middleware, Merchant $merchant, RequestReviewStatuses $request_review_statuses, TransientsInterface $transients ) {
parent::__construct( $server );
$this->middleware = $middleware;
$this->merchant = $merchant;
$this->request_review_statuses = $request_review_statuses;
$this->transients = $transients;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
/**
* GET information regarding the current Account Status
*/
$this->register_route(
'mc/review',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_review_read_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
/**
* POST a request review for the current account
*/
$this->register_route(
'mc/review',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->post_review_request_callback(),
'permission_callback' => $this->get_permission_callback(),
],
],
);
}
/**
* Get the callback function for returning the review status.
*
* @return callable
*/
protected function get_review_read_callback(): callable {
return function ( Request $request ) {
try {
return $this->prepare_item_for_response( $this->get_review_status(), $request );
} catch ( Exception $e ) {
return new Response( [ 'message' => $e->getMessage() ], $e->getCode() ?: 400 );
}
};
}
/**
* Get the callback function after requesting a review.
*
* @return callable
*/
protected function post_review_request_callback(): callable {
return function () {
try {
// getting the current account status
$account_review_status = $this->get_review_status();
// Abort if it's in cool down period
if ( $account_review_status['cooldown'] ) {
do_action(
'woocommerce_gla_request_review_failure',
[
'error' => 'cooldown',
'account_review_status' => $account_review_status,
]
);
throw new Exception( __( 'Your account is under cool down period and cannot request a new review.', 'google-listings-and-ads' ), 400 );
}
// Abort if there is no eligible region available
if ( ! count( $account_review_status['reviewEligibleRegions'] ) ) {
do_action(
'woocommerce_gla_request_review_failure',
[
'error' => 'ineligible',
'account_review_status' => $account_review_status,
]
);
throw new Exception( __( 'Your account is not eligible for a new request review.', 'google-listings-and-ads' ), 400 );
}
$this->account_request_review( $account_review_status['reviewEligibleRegions'] );
return $this->set_under_review_status();
} catch ( Exception $e ) {
/**
* Catch potential errors in any specific region API call.
*
* Notice due some inconsistencies with Google API we are not considering [Bad Request -> ...already under review...]
* as an exception. This is because we suspect that calling the API of a region is triggering other regions requests as well.
* This makes all the calls after the first to fail as they will be under review.
*
* The undesired call of this function for accounts under review is already prevented in a previous stage, so, there is no danger doing this.
*/
if ( strpos( $e->getMessage(), 'under review' ) !== false ) {
return $this->set_under_review_status();
}
return new Response( [ 'message' => $e->getMessage() ], $e->getCode() ?: 400 );
}
};
}
/**
* Set Under review Status in the cache and return the response
*
* @return Response With the Under review status
*/
private function set_under_review_status() {
$new_status = [
'issues' => [],
'cooldown' => 0,
'status' => $this->request_review_statuses::UNDER_REVIEW,
'reviewEligibleRegions' => [],
];
// Update Account status when successful response
$this->set_cached_review_status( $new_status );
return new Response( $new_status );
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'status' => [
'type' => 'string',
'description' => __( 'The status of the last review.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'cooldown' => [
'type' => 'integer',
'description' => __( 'Timestamp indicating if the user is in cool down period.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'issues' => [
'type' => 'array',
'description' => __( 'The issues related to the Merchant Center to be reviewed and addressed before approval.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
'items' => [
'type' => 'string',
],
],
'reviewEligibleRegions' => [
'type' => 'array',
'description' => __( 'The region codes in which is allowed to request a new review.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
'items' => [
'type' => 'string',
],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'merchant_account_review';
}
/**
* Save the Account Review Status data inside a transient for caching purposes.
*
* @param array $value The Account Review Status data to save in the transient
*/
private function set_cached_review_status( $value ): void {
$this->transients->set(
TransientsInterface::MC_ACCOUNT_REVIEW,
$value,
$this->request_review_statuses->get_account_review_lifetime()
);
}
/**
* Get the Account Review Status data inside a transient for caching purposes.
*
* @return null|array Returns NULL in case no data is available or an array with the Account Review Status data otherwise.
*/
private function get_cached_review_status(): ?array {
return $this->transients->get(
TransientsInterface::MC_ACCOUNT_REVIEW,
);
}
/**
* Get the Account Review Status. We attempt to get the cached version or create a request otherwise.
*
* @return null|array Returns NULL in case no data is available or an array with the Account Review Status data otherwise.
* @throws Exception If the get_account_review_status API call fails.
*/
private function get_review_status(): ?array {
$review_status = $this->get_cached_review_status();
if ( is_null( $review_status ) ) {
$response = $this->get_account_review_status();
$review_status = $this->request_review_statuses->get_statuses_from_response( $response );
$this->set_cached_review_status( $review_status );
}
return $review_status;
}
/**
* Get Account Review Status
*
* @return array the response data
* @throws Exception When there is an invalid response.
*/
public function get_account_review_status() {
try {
if ( ! $this->middleware->is_subaccount() ) {
return [];
}
$response = $this->merchant->get_account_review_status();
do_action( 'woocommerce_gla_request_review_response', $response );
return $response;
} catch ( Exception $e ) {
do_action( 'woocommerce_gla_exception', $e, __METHOD__ );
throw new Exception(
$e->getMessage() ?? __( 'Error getting account review status.', 'google-listings-and-ads' ),
$e->getCode()
);
}
}
/**
* Request a new account review
*
* @param array $regions Regions to request a review.
* @return array With a successful message
* @throws Exception When there is an invalid response.
*/
public function account_request_review( $regions ) {
try {
// For each region we request a new review
foreach ( $regions as $region_code => $region_types ) {
$result = $this->merchant->account_request_review( $region_code, $region_types );
if ( 200 !== $result->getStatusCode() ) {
do_action(
'woocommerce_gla_request_review_failure',
[
'error' => 'response',
'region_code' => $region_code,
'response' => $result,
]
);
do_action( 'woocommerce_gla_guzzle_invalid_response', $result, __METHOD__ );
$error = $response['message'] ?? __( 'Invalid response getting requesting a new review.', 'google-listings-and-ads' );
throw new Exception( $error, $result->getStatusCode() );
}
}
// Otherwise, return a successful message and update the account status
return [
'message' => __( 'A new review has been successfully requested', 'google-listings-and-ads' ),
];
} catch ( Exception $e ) {
do_action( 'woocommerce_gla_exception', $e, __METHOD__ );
throw new Exception(
$e->getMessage() ?? __( 'Error requesting a new review.', 'google-listings-and-ads' ),
$e->getCode()
);
}
}
}
Controllers/MerchantCenter/SettingsController.php 0000644 00000012231 15155033414 0016331 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Shipping\ShippingZone;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class SettingsController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class SettingsController extends BaseOptionsController {
/**
* @var ShippingZone
*/
protected $shipping_zone;
/**
* SettingsController constructor.
*
* @param RESTServer $server
* @param ShippingZone $shipping_zone
*/
public function __construct( RESTServer $server, ShippingZone $shipping_zone ) {
parent::__construct( $server );
$this->shipping_zone = $shipping_zone;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/settings',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_settings_endpoint_read_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::EDITABLE,
'callback' => $this->get_settings_endpoint_edit_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get a callback for the settings endpoint.
*
* @return callable
*/
protected function get_settings_endpoint_read_callback(): callable {
return function () {
$data = $this->options->get( OptionsInterface::MERCHANT_CENTER, [] );
$data['shipping_rates_count'] = $this->shipping_zone->get_shipping_rates_count();
$schema = $this->get_schema_properties();
$items = [];
foreach ( $schema as $key => $property ) {
$items[ $key ] = $data[ $key ] ?? $property['default'] ?? null;
}
return $items;
};
}
/**
* Get a callback for editing the settings endpoint.
*
* @return callable
*/
protected function get_settings_endpoint_edit_callback(): callable {
return function ( Request $request ) {
$schema = $this->get_schema_properties();
$options = $this->options->get( OptionsInterface::MERCHANT_CENTER, [] );
if ( ! is_array( $options ) ) {
$options = [];
}
foreach ( $schema as $key => $property ) {
if ( ! in_array( 'edit', $property['context'] ?? [], true ) ) {
continue;
}
$options[ $key ] = $request->get_param( $key ) ?? $options[ $key ] ?? $property['default'] ?? null;
}
$this->options->update( OptionsInterface::MERCHANT_CENTER, $options );
return [
'status' => 'success',
'message' => __( 'Merchant Center Settings successfully updated.', 'google-listings-and-ads' ),
'data' => $options,
];
};
}
/**
* Get the schema for settings endpoints.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'shipping_rate' => [
'type' => 'string',
'description' => __(
'Whether shipping rate is a simple flat rate or needs to be configured manually in the Merchant Center.',
'google-listings-and-ads'
),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'enum' => [
'automatic',
'flat',
'manual',
],
],
'shipping_time' => [
'type' => 'string',
'description' => __(
'Whether shipping time is a simple flat time or needs to be configured manually in the Merchant Center.',
'google-listings-and-ads'
),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'enum' => [
'flat',
'manual',
],
],
'tax_rate' => [
'type' => 'string',
'description' => __(
'Whether tax rate is destination based or need to be configured manually in the Merchant Center.',
'google-listings-and-ads'
),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'enum' => [
'destination',
'manual',
],
'default' => 'destination',
],
'shipping_rates_count' => [
'type' => 'number',
'description' => __(
'The number of shipping rates in WC ready to be used in the Merchant Center.',
'google-listings-and-ads'
),
'context' => [ 'view' ],
'validate_callback' => 'rest_validate_request_arg',
'default' => 0,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'merchant_center_settings';
}
}
Controllers/MerchantCenter/SettingsSyncController.php 0000644 00000007063 15155033414 0017175 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Settings;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\EmptySchemaPropertiesTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\WPErrorTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class SettingsSyncController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class SettingsSyncController extends BaseController {
use EmptySchemaPropertiesTrait;
use WPErrorTrait;
/** @var Settings */
protected $settings;
/**
* SettingsSyncController constructor.
*
* @param RESTServer $server
* @param Settings $settings
*/
public function __construct( RESTServer $server, Settings $settings ) {
parent::__construct( $server );
$this->settings = $settings;
}
/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
$this->register_route(
'mc/settings/sync',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_sync_endpoint_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback for syncing shipping.
*
* @return callable
*/
protected function get_sync_endpoint_callback(): callable {
return function ( Request $request ) {
try {
$this->settings->sync_taxes();
$this->settings->sync_shipping();
do_action( 'woocommerce_gla_mc_settings_sync' );
/**
* MerchantCenter onboarding has been successfully completed.
*
* @event gla_mc_setup_completed
* @property string shipping_rate Shipping rate setup `automatic`, `manual`, `flat`.
* @property bool offers_free_shipping Free Shipping is available.
* @property float free_shipping_threshold Minimum amount to avail of free shipping.
* @property string shipping_time Shipping time setup `flat`, `manual`.
* @property string tax_rate Tax rate setup `destination`, `manual`.
* @property string target_countries List of target countries or `all`.
*/
do_action(
'woocommerce_gla_track_event',
'mc_setup_completed',
$this->settings->get_settings_for_tracking()
);
return new Response(
[
'status' => 'success',
'message' => __( 'Successfully synchronized settings with Google.', 'google-listings-and-ads' ),
],
201
);
} catch ( Exception $e ) {
do_action( 'woocommerce_gla_exception', $e, __METHOD__ );
try {
$decoded = $this->json_decode_message( $e->getMessage() );
$data = [
'status' => $decoded['code'] ?? 500,
'message' => $decoded['message'] ?? '',
'data' => $decoded,
];
} catch ( Exception $e2 ) {
$data = [
'status' => 500,
];
}
return $this->error_from_exception(
$e,
'gla_setting_sync_error',
$data
);
}
};
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'settings_sync';
}
}
Controllers/MerchantCenter/ShippingRateBatchController.php 0000644 00000010127 15155033414 0020072 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingRateBatchController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ShippingRateBatchController extends ShippingRateController {
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
"{$this->route_base}/batch",
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_batch_create_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_batch_create_args_schema(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->get_batch_delete_shipping_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_batch_delete_args_schema(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback for creating items via batch.
*
* @return callable
*/
protected function get_batch_create_callback(): callable {
return function ( Request $request ) {
$rates = $request->get_param( 'rates' );
$responses = [];
$errors = [];
foreach ( $rates as $rate ) {
$new_request = new Request( 'POST', "/{$this->get_namespace()}/{$this->route_base}" );
$new_request->set_body_params( $rate );
$response = $this->server->dispatch_request( $new_request );
if ( 201 !== $response->get_status() ) {
$errors[] = $response->get_data();
} else {
$responses[] = $response->get_data();
}
}
return new Response(
[
'errors' => $errors,
'success' => $responses,
],
201
);
};
}
/**
* Get the callback for deleting shipping items via batch.
*
* @return callable
*
* @since 1.12.0
*/
protected function get_batch_delete_shipping_callback(): callable {
return function ( Request $request ) {
$ids = $request->get_param( 'ids' );
$responses = [];
$errors = [];
foreach ( $ids as $id ) {
$route = "/{$this->get_namespace()}/{$this->route_base}/{$id}";
$delete_request = new Request( 'DELETE', $route );
$response = $this->server->dispatch_request( $delete_request );
if ( 200 !== $response->get_status() ) {
$errors[] = $response->get_data();
} else {
$responses[] = $response->get_data();
}
}
return new Response(
[
'errors' => $errors,
'success' => $responses,
],
);
};
}
/**
* Get the argument schema for a batch create request.
*
* @return array
*
* @since 1.12.0
*/
protected function get_batch_create_args_schema(): array {
return [
'rates' => [
'type' => 'array',
'minItems' => 1,
'uniqueItems' => true,
'description' => __( 'Array of shipping rates to create.', 'google-listings-and-ads' ),
'validate_callback' => 'rest_validate_request_arg',
'items' => [
'type' => 'object',
'additionalProperties' => false,
'properties' => $this->get_schema_properties(),
],
],
];
}
/**
* Get the argument schema for a batch delete request.
*
* @return array
*
* @since 1.12.0
*/
protected function get_batch_delete_args_schema(): array {
return [
'ids' => [
'type' => 'array',
'description' => __( 'Array of unique shipping rate identification numbers.', 'google-listings-and-ads' ),
'context' => [ 'edit' ],
'minItems' => 1,
'required' => true,
'uniqueItems' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'batch_shipping_rates';
}
}
Controllers/MerchantCenter/ShippingRateController.php 0000644 00000020133 15155033414 0017126 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\ShippingRateSchemaTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\ShippingRateQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ISO3166AwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingRateController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ShippingRateController extends BaseController implements ISO3166AwareInterface {
use ShippingRateSchemaTrait;
/**
* The base for routes in this controller.
*
* @var string
*/
protected $route_base = 'mc/shipping/rates';
/**
* @var ShippingRateQuery
*/
protected $query;
/**
* ShippingRateController constructor.
*
* @param RESTServer $server
* @param ShippingRateQuery $query
*/
public function __construct( RESTServer $server, ShippingRateQuery $query ) {
parent::__construct( $server );
$this->query = $query;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
$this->route_base,
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_read_all_rates_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_create_rate_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
"{$this->route_base}/(?P<id>[\d]+)",
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_read_rate_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => [ 'id' => $this->get_schema_properties()['id'] ],
],
[
'methods' => TransportMethods::EDITABLE,
'callback' => $this->get_update_rate_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->get_delete_rate_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => [ 'id' => $this->get_schema_properties()['id'] ],
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for returning the endpoint results.
*
* @return callable
*/
protected function get_read_all_rates_callback(): callable {
return function ( Request $request ) {
$rates = $this->get_all_shipping_rates();
return array_map(
function ( $rate ) use ( $request ) {
$response = $this->prepare_item_for_response( $rate, $request );
return $this->prepare_response_for_collection( $response );
},
$rates
);
};
}
/**
* @return callable
*/
protected function get_read_rate_callback(): callable {
return function ( Request $request ) {
$id = (string) $request->get_param( 'id' );
$rate = $this->get_shipping_rate_by_id( $id );
if ( empty( $rate ) ) {
return new Response(
[
'message' => __( 'No rate available.', 'google-listings-and-ads' ),
'id' => $id,
],
404
);
}
return $this->prepare_item_for_response( $rate, $request );
};
}
/**
* @return callable
*
* @since 1.12.0
*/
protected function get_update_rate_callback(): callable {
return function ( Request $request ) {
$id = (string) $request->get_param( 'id' );
$rate = $this->get_shipping_rate_by_id( $id );
if ( empty( $rate ) ) {
return new Response(
[
'message' => __( 'No rate found with the given ID.', 'google-listings-and-ads' ),
'id' => $id,
],
404
);
}
$data = $this->prepare_item_for_database( $request );
$this->create_query()->update(
$data,
[
'id' => $id,
]
);
return new Response( '', 204 );
};
}
/**
* Get the callback function for creating a new shipping rate.
*
* @return callable
*/
protected function get_create_rate_callback(): callable {
return function ( Request $request ) {
$shipping_rate_query = $this->create_query();
try {
$data = $this->prepare_item_for_database( $request );
$country = $data['country'];
$existing_query = $this->create_query()->where( 'country', $country );
$existing = ! empty( $existing_query->get_results() );
if ( $existing ) {
$rate_id = $existing_query->get_results()[0]['id'];
$shipping_rate_query->update( $data, [ 'id' => $rate_id ] );
} else {
$shipping_rate_query->insert( $data );
$rate_id = $shipping_rate_query->last_insert_id();
}
} catch ( InvalidQuery $e ) {
return $this->error_from_exception(
$e,
'gla_error_creating_shipping_rate',
[
'code' => 400,
'message' => $e->getMessage(),
]
);
}
// Fetch updated/inserted rate to return in response.
$rate_response = $this->prepare_item_for_response(
$this->get_shipping_rate_by_id( (string) $rate_id ),
$request
);
return new Response(
[
'status' => 'success',
'message' => sprintf(
/* translators: %s is the country code in ISO 3166-1 alpha-2 format. */
__( 'Successfully added rate for country: "%s".', 'google-listings-and-ads' ),
$country
),
'rate' => $rate_response->get_data(),
],
201
);
};
}
/**
* @return callable
*/
protected function get_delete_rate_callback(): callable {
return function ( Request $request ) {
try {
$id = (string) $request->get_param( 'id' );
$rate = $this->get_shipping_rate_by_id( $id );
if ( empty( $rate ) ) {
return new Response(
[
'message' => __( 'No rate found with the given ID.', 'google-listings-and-ads' ),
'id' => $id,
],
404
);
}
$this->create_query()->delete( 'id', $id );
return [
'status' => 'success',
'message' => __( 'Successfully deleted rate.', 'google-listings-and-ads' ),
];
} catch ( InvalidQuery $e ) {
return $this->error_from_exception(
$e,
'gla_error_deleting_shipping_rate',
[
'code' => 400,
'message' => $e->getMessage(),
]
);
}
};
}
/**
* Returns the list of all shipping rates stored in the database grouped by their respective country code.
*
* @return array Array of shipping rates grouped by country code.
*/
protected function get_all_shipping_rates(): array {
return $this->create_query()
->set_order( 'country', 'ASC' )
->get_results();
}
/**
* @param string $id
*
* @return array|null The shipping rate properties as an array or null if it doesn't exist.
*/
protected function get_shipping_rate_by_id( string $id ): ?array {
$results = $this->create_query()->where( 'id', $id )->get_results();
return ! empty( $results ) ? $results[0] : null;
}
/**
* Return a new instance of the shipping rate query object.
*
* @return ShippingRateQuery
*/
protected function create_query(): ShippingRateQuery {
return clone $this->query;
}
/**
* @return array
*/
protected function get_schema_properties(): array {
return $this->get_shipping_rate_schema();
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'shipping_rates';
}
}
Controllers/MerchantCenter/ShippingRateSuggestionsController.php 0000644 00000007755 15155033414 0021400 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\ShippingRateSchemaTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ISO3166AwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Shipping\ShippingSuggestionService;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingRateSuggestionsController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*
* @since 1.12.0
*/
class ShippingRateSuggestionsController extends BaseController implements ISO3166AwareInterface {
use ShippingRateSchemaTrait;
/**
* The base for routes in this controller.
*
* @var string
*/
protected $route_base = 'mc/shipping/rates/suggestions';
/**
* @var ShippingSuggestionService
*/
protected $shipping_suggestion;
/**
* ShippingRateSuggestionsController constructor.
*
* @param RESTServer $server
* @param ShippingSuggestionService $shipping_suggestion
*/
public function __construct( RESTServer $server, ShippingSuggestionService $shipping_suggestion ) {
parent::__construct( $server );
$this->shipping_suggestion = $shipping_suggestion;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
"{$this->route_base}",
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_suggestions_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => [
'country_codes' => [
'type' => 'array',
'description' => __( 'Array of country codes in ISO 3166-1 alpha-2 format.', 'google-listings-and-ads' ),
'context' => [ 'edit' ],
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_country_code_validate_callback(),
'minItems' => 1,
'required' => true,
'uniqueItems' => true,
'items' => [
'type' => 'string',
],
],
],
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for returning the endpoint results.
*
* @return callable
*/
protected function get_suggestions_callback(): callable {
return function ( Request $request ) {
$country_codes = $request->get_param( 'country_codes' );
$rates_output = [];
foreach ( $country_codes as $country_code ) {
$suggestions = $this->shipping_suggestion->get_suggestions( $country_code );
// Prepare the output.
$suggestions = array_map(
function ( $suggestion ) use ( $request ) {
$response = $this->prepare_item_for_response( $suggestion, $request );
return $this->prepare_response_for_collection( $response );
},
$suggestions
);
// Merge the suggestions for all countries into one array.
$rates_output = array_merge( $rates_output, $suggestions );
}
return $rates_output;
};
}
/**
* @return array
*/
protected function get_schema_properties(): array {
$schema = $this->get_shipping_rate_schema();
// Suggested shipping rates don't have an id.
unset( $schema['id'] );
// All properties are read-only.
return array_map(
function ( $property ) {
$property['readonly'] = true;
$property['context'] = [ 'view' ];
return $property;
},
$schema
);
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'shipping_rates_suggestions';
}
}
Controllers/MerchantCenter/ShippingTimeBatchController.php 0000644 00000005132 15155033414 0020075 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BatchSchemaTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingTimeBatchController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ShippingTimeBatchController extends ShippingTimeController {
use BatchSchemaTrait;
use BatchShippingTrait;
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
"{$this->route_base}/batch",
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_batch_create_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_item_schema(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->get_batch_delete_shipping_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_item_delete_schema(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback for creating items via batch.
*
* @return callable
*/
protected function get_batch_create_callback(): callable {
return function ( Request $request ) {
$country_codes = $request->get_param( 'country_codes' );
$time = $request->get_param( 'time' );
$max_time = $request->get_param( 'max_time' );
$responses = [];
$errors = [];
foreach ( $country_codes as $country_code ) {
$new_request = new Request( 'POST', "/{$this->get_namespace()}/{$this->route_base}" );
$new_request->set_body_params(
[
'country_code' => $country_code,
'time' => $time,
'max_time' => $max_time,
]
);
$response = $this->server->dispatch_request( $new_request );
if ( 201 !== $response->get_status() ) {
$errors[] = $response->get_data();
} else {
$responses[] = $response->get_data();
}
}
return new Response(
[
'errors' => $errors,
'success' => $responses,
],
201
);
};
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'batch_shipping_times';
}
}
Controllers/MerchantCenter/ShippingTimeController.php 0000644 00000023537 15155033414 0017144 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\CountryCodeTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\ShippingTimeQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\ContainerAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ContainerAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ISO3166AwareInterface;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
use WP_Error;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingTimeController
*
* ContainerAware used for:
* - ShippingTimeQuery
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class ShippingTimeController extends BaseController implements ContainerAwareInterface, ISO3166AwareInterface {
use ContainerAwareTrait;
use CountryCodeTrait;
/**
* The base for routes in this controller.
*
* @var string
*/
protected $route_base = 'mc/shipping/times';
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
$this->route_base,
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_read_times_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_create_time_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_args_schema(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
"{$this->route_base}/(?P<country_code>\\w{2})",
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_read_time_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->get_delete_time_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for reading times.
*
* @return callable
*/
protected function get_read_times_callback(): callable {
return function ( Request $request ) {
$times = $this->get_all_shipping_times();
$items = [];
foreach ( $times as $time ) {
$data = $this->prepare_item_for_response(
[
'country_code' => $time['country'],
'time' => $time['time'],
'max_time' => $time['max_time'],
],
$request
);
$items[ $time['country'] ] = $this->prepare_response_for_collection( $data );
}
return $items;
};
}
/**
* Get the callback function for reading a single time.
*
* @return callable
*/
protected function get_read_time_callback(): callable {
return function ( Request $request ) {
$country = $request->get_param( 'country_code' );
$time = $this->get_shipping_time_for_country( $country );
if ( empty( $time ) ) {
return new Response(
[
'message' => __( 'No time available.', 'google-listings-and-ads' ),
'country' => $country,
],
404
);
}
return $this->prepare_item_for_response(
[
'country_code' => $time[0]['country'],
'time' => $time[0]['time'],
'max_time' => $time[0]['max_time'],
],
$request
);
};
}
/**
* Get the callback to crate a new time.
*
* @return callable
*/
protected function get_create_time_callback(): callable {
return function ( Request $request ) {
$query = $this->get_query_object();
$country_code = $request->get_param( 'country_code' );
$existing = ! empty( $query->where( 'country', $country_code )->get_results() );
try {
$data = [
'country' => $country_code,
'time' => $request->get_param( 'time' ),
'max_time' => $request->get_param( 'max_time' ),
];
if ( $existing ) {
$query->update(
$data,
[
'id' => $query->get_results()[0]['id'],
]
);
} else {
$query->insert( $data );
}
return new Response(
[
'status' => 'success',
'message' => sprintf(
/* translators: %s is the country code in ISO 3166-1 alpha-2 format. */
__( 'Successfully added time for country: "%s".', 'google-listings-and-ads' ),
$country_code
),
],
201
);
} catch ( InvalidQuery $e ) {
return $this->error_from_exception(
$e,
'gla_error_creating_shipping_time',
[
'code' => 400,
'message' => $e->getMessage(),
]
);
}
};
}
/**
* Get the callback function for deleting a time.
*
* @return callable
*/
protected function get_delete_time_callback(): callable {
return function ( Request $request ) {
try {
$country_code = $request->get_param( 'country_code' );
$this->get_query_object()->delete( 'country', $country_code );
return [
'status' => 'success',
'message' => sprintf(
/* translators: %s is the country code in ISO 3166-1 alpha-2 format. */
__( 'Successfully deleted the time for country: "%s".', 'google-listings-and-ads' ),
$country_code
),
];
} catch ( InvalidQuery $e ) {
return $this->error_from_exception(
$e,
'gla_error_deleting_shipping_time',
[
'code' => 400,
'message' => $e->getMessage(),
]
);
}
};
}
/**
* @return array
*/
protected function get_all_shipping_times(): array {
return $this->get_query_object()->set_limit( 100 )->get_results();
}
/**
* @param string $country
*
* @return array
*/
protected function get_shipping_time_for_country( string $country ): array {
return $this->get_query_object()->where( 'country', $country )->get_results();
}
/**
* Get the shipping time query object.
*
* @return ShippingTimeQuery
*/
protected function get_query_object(): ShippingTimeQuery {
return $this->container->get( ShippingTimeQuery::class );
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'country_code' => [
'type' => 'string',
'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_country_code_validate_callback(),
'required' => true,
],
'time' => [
'type' => 'integer',
'description' => __( 'The minimum shipping time in days.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => [ $this, 'validate_shipping_times' ],
],
'max_time' => [
'type' => 'integer',
'description' => __( 'The maximum shipping time in days.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => [ $this, 'validate_shipping_times' ],
],
];
}
/**
* Get the args schema for the controller.
*
* @return array
*/
protected function get_args_schema(): array {
$schema = $this->get_schema_properties();
$schema['time']['required'] = true;
$schema['max_time']['required'] = true;
return $schema;
}
/**
* Validate the shipping times.
*
* @param mixed $value
* @param Request $request
* @param string $param
*
* @return WP_Error|true
*/
public function validate_shipping_times( $value, $request, $param ) {
$time = $request->get_param( 'time' );
$max_time = $request->get_param( 'max_time' );
if ( rest_is_integer( $value ) === false ) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf( __( '%1$s is not of type %2$s.', 'google-listings-and-ads' ), $param, 'integer' ),
[ 'param' => $param ]
);
}
if ( $value < 0 ) {
return new WP_Error( 'invalid_shipping_times', __( 'Shipping times cannot be negative.', 'google-listings-and-ads' ), [ 'param' => $param ] );
}
if ( $time > $max_time ) {
return new WP_Error( 'invalid_shipping_times', __( 'The minimum shipping time cannot be greater than the maximum shipping time.', 'google-listings-and-ads' ), [ 'param' => $param ] );
}
return true;
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'shipping_times';
}
/**
* Retrieves all of the registered additional fields for a given object-type.
*
* @param string $object_type Optional. The object type.
*
* @return array Registered additional fields (if any), empty array if none or if the object type could
* not be inferred.
*/
protected function get_additional_fields( $object_type = null ): array {
$fields = parent::get_additional_fields( $object_type );
$fields['country'] = [
'schema' => [
'type' => 'string',
'description' => __( 'Country in which the shipping time applies.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'get_callback' => function ( $fields ) {
return $this->iso3166_data_provider->alpha2( $fields['country_code'] )['name'];
},
];
return $fields;
}
}
Controllers/MerchantCenter/SupportedCountriesController.php 0000644 00000010101 15155033414 0020404 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\CountryCodeTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\EmptySchemaPropertiesTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\GoogleHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class SupportedCountriesController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
class SupportedCountriesController extends BaseController {
use CountryCodeTrait;
use EmptySchemaPropertiesTrait;
/**
* The WC proxy object.
*
* @var WC
*/
protected $wc;
/**
* @var GoogleHelper
*/
protected $google_helper;
/**
* SupportedCountriesController constructor.
*
* @param RESTServer $server
* @param WC $wc
* @param GoogleHelper $google_helper
*/
public function __construct( RESTServer $server, WC $wc, GoogleHelper $google_helper ) {
parent::__construct( $server );
$this->wc = $wc;
$this->google_helper = $google_helper;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/countries',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_countries_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_query_args(),
],
]
);
}
/**
* Get the callback function for returning supported countries.
*
* @return callable
*/
protected function get_countries_callback(): callable {
return function ( Request $request ) {
$return = [
'countries' => $this->get_supported_countries( $request ),
];
if ( $request->get_param( 'continents' ) ) {
$return['continents'] = $this->get_supported_continents();
}
return $return;
};
}
/**
* Get the array of supported countries.
*
* @return array
*/
protected function get_supported_countries(): array {
$all_countries = $this->wc->get_countries();
$mc_countries = $this->google_helper->get_mc_supported_countries_currencies();
$supported = [];
foreach ( $mc_countries as $country => $currency ) {
if ( ! array_key_exists( $country, $all_countries ) ) {
continue;
}
$supported[ $country ] = [
'name' => $all_countries[ $country ],
'currency' => $currency,
];
}
uasort(
$supported,
function ( $a, $b ) {
return $a['name'] <=> $b['name'];
}
);
return $supported;
}
/**
* Get the array of supported continents.
*
* @return array
*/
protected function get_supported_continents(): array {
$all_continents = $this->wc->get_continents();
foreach ( $all_continents as $continent_code => $continent ) {
$supported_countries_of_continent = $this->google_helper->get_supported_countries_from_continent( $continent_code );
if ( empty( $supported_countries_of_continent ) ) {
unset( $all_continents[ $continent_code ] );
} else {
$all_continents[ $continent_code ]['countries'] = array_values( $supported_countries_of_continent );
}
}
return $all_continents;
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'supported_countries';
}
/**
* Get the arguments for the query endpoint.
*
* @return array
*/
protected function get_query_args(): array {
return [
'continents' => [
'description' => __( 'Include continents data if set to true.', 'google-listings-and-ads' ),
'type' => 'boolean',
'validate_callback' => 'rest_validate_request_arg',
],
];
}
}
Controllers/MerchantCenter/SyncableProductsCountController.php 0000644 00000007025 15155033414 0021033 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\UpdateSyncableProductsCount;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
defined( 'ABSPATH' ) || exit;
/**
* Class SyncableProductsCountController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class SyncableProductsCountController extends BaseOptionsController {
/**
* @var JobRepository
*/
protected $job_repository;
/**
* SyncableProductsCountController constructor.
*
* @param RESTServer $server
* @param JobRepository $job_repository
*/
public function __construct( RESTServer $server, JobRepository $job_repository ) {
parent::__construct( $server );
$this->job_repository = $job_repository;
}
/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
$this->register_route(
'mc/syncable-products-count',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_syncable_products_count_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->update_syncable_products_count_callback(),
'permission_callback' => $this->get_permission_callback(),
],
]
);
}
/**
* Get the callback function for marking setup complete.
*
* @return callable
*/
protected function get_syncable_products_count_callback(): callable {
return function ( Request $request ) {
$response = [
'count' => null,
];
$count = $this->options->get( OptionsInterface::SYNCABLE_PRODUCTS_COUNT );
if ( isset( $count ) ) {
$response['count'] = (int) $count;
}
return $this->prepare_item_for_response( $response, $request );
};
}
/**
* Get the callback for syncing shipping.
*
* @return callable
*/
protected function update_syncable_products_count_callback(): callable {
return function ( Request $request ) {
$this->options->delete( OptionsInterface::SYNCABLE_PRODUCTS_COUNT );
$this->options->delete( OptionsInterface::SYNCABLE_PRODUCTS_COUNT_INTERMEDIATE_DATA );
$job = $this->job_repository->get( UpdateSyncableProductsCount::class );
$job->schedule();
return new Response(
[
'status' => 'success',
'message' => __( 'Successfully scheduled a job to update the number of syncable products.', 'google-listings-and-ads' ),
],
201
);
};
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'count' => [
'type' => 'number',
'description' => __( 'The number of products that are ready to be synced to Google.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'syncable_products_count';
}
}
Controllers/MerchantCenter/TargetAudienceController.php 0000644 00000020277 15155033414 0017426 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\CountryCodeTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\GoogleHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ISO3166AwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WP;
use Automattic\WooCommerce\GoogleListingsAndAds\Shipping\ShippingZone;
use Locale;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
use function wp_get_available_translations;
defined( 'ABSPATH' ) || exit;
/**
* Class TargetAudienceController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
*/
class TargetAudienceController extends BaseOptionsController implements ISO3166AwareInterface {
use CountryCodeTrait;
/**
* The WP proxy object.
*
* @var WP
*/
protected $wp;
/**
* @var ShippingZone
*/
protected $shipping_zone;
/**
* @var WC
*/
protected $wc;
/**
* @var GoogleHelper
*/
protected $google_helper;
/**
* TargetAudienceController constructor.
*
* @param RESTServer $server
* @param WP $wp
* @param WC $wc
* @param ShippingZone $shipping_zone
* @param GoogleHelper $google_helper
*/
public function __construct( RESTServer $server, WP $wp, WC $wc, ShippingZone $shipping_zone, GoogleHelper $google_helper ) {
parent::__construct( $server );
$this->wp = $wp;
$this->wc = $wc;
$this->shipping_zone = $shipping_zone;
$this->google_helper = $google_helper;
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'mc/target_audience',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_read_audience_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_update_audience_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
$this->register_route(
'mc/target_audience/suggestions',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_suggest_audience_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for reading the target audience data.
*
* @return callable
*/
protected function get_read_audience_callback(): callable {
return function ( Request $request ) {
return $this->prepare_item_for_response( $this->get_target_audience_option(), $request );
};
}
/**
* Get the callback function for suggesting the target audience data.
*
* @return callable
*
* @since 1.9.0
*/
protected function get_suggest_audience_callback(): callable {
return function ( Request $request ) {
return $this->prepare_item_for_response( $this->get_target_audience_suggestion(), $request );
};
}
/**
* Get the callback function for updating the target audience data.
*
* @return callable
*/
protected function get_update_audience_callback(): callable {
return function ( Request $request ) {
$data = $this->prepare_item_for_database( $request );
$this->update_target_audience_option( $data );
$this->prepare_item_for_response( $data, $request );
return new Response(
[
'status' => 'success',
'message' => __( 'Successfully updated the Target Audience settings.', 'google-listings-and-ads' ),
],
201
);
};
}
/**
* Retrieves all of the registered additional fields for a given object-type.
*
* @param string $object_type Optional. The object type.
*
* @return array Registered additional fields (if any), empty array if none or if the object type could
* not be inferred.
*/
protected function get_additional_fields( $object_type = null ): array {
$fields = parent::get_additional_fields( $object_type );
// Fields are expected to be an array with a 'get_callback' callable that returns the field value.
$fields['locale'] = [
'schema' => [
'type' => 'string',
'description' => __( 'The locale for the site.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'get_callback' => function () {
return $this->wp->get_locale();
},
];
$fields['language'] = [
'schema' => [
'type' => 'string',
'description' => __( 'The language to use for product listings.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'get_callback' => $this->get_language_callback(),
];
return $fields;
}
/**
* Get the option data for the target audience.
*
* @return array
*/
protected function get_target_audience_option(): array {
return $this->options->get( OptionsInterface::TARGET_AUDIENCE, [] );
}
/**
* Get the suggested values for the target audience option.
*
* @return string[]
*
* @since 1.9.0
*/
protected function get_target_audience_suggestion(): array {
$countries = $this->shipping_zone->get_shipping_countries();
$base_country = $this->wc->get_base_country();
// Add WooCommerce store country if it's supported and not already in the list.
if ( ! in_array( $base_country, $countries, true ) && $this->google_helper->is_country_supported( $base_country ) ) {
$countries[] = $base_country;
}
return [
'location' => 'selected',
'countries' => $countries,
];
}
/**
* Update the option data for the target audience.
*
* @param array $data
*
* @return bool
*/
protected function update_target_audience_option( array $data ): bool {
return $this->options->update( OptionsInterface::TARGET_AUDIENCE, $data );
}
/**
* Get the item schema for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'location' => [
'type' => 'string',
'description' => __( 'Location where products will be shown.', 'google-listings-and-ads' ),
'context' => [ 'edit', 'view' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
'enum' => [
'all',
'selected',
],
],
'countries' => [
'type' => 'array',
'description' => __(
'Array of country codes in ISO 3166-1 alpha-2 format.',
'google-listings-and-ads'
),
'context' => [ 'edit', 'view' ],
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_country_code_validate_callback(),
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'target_audience';
}
/**
* Get the callback to provide the language in use for the site.
*
* @return callable
*/
protected function get_language_callback(): callable {
$locale = $this->wp->get_locale();
// Default to using the Locale class if it is available.
if ( class_exists( Locale::class ) ) {
return function () use ( $locale ): string {
return Locale::getDisplayLanguage( $locale, $locale );
};
}
return function () use ( $locale ): string {
// en_US isn't provided by the translations API.
if ( 'en_US' === $locale ) {
return 'English';
}
require_once ABSPATH . 'wp-admin/includes/translation-install.php';
return wp_get_available_translations()[ $locale ]['native_name'] ?? $locale;
};
}
}
Controllers/ResponseFromExceptionTrait.php 0000644 00000001641 15155033414 0015073 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData;
use Exception;
use WP_REST_Response as Response;
/**
* Trait ResponseFromExceptionTrait
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*
* @since 1.5.0
*/
trait ResponseFromExceptionTrait {
/**
* Get REST response from an exception.
*
* @param Exception $exception
*
* @return Response
*/
protected function response_from_exception( Exception $exception ): Response {
$code = $exception->getCode();
$status = $code && is_numeric( $code ) ? $code : 400;
if ( $exception instanceof ExceptionWithResponseData ) {
return new Response( $exception->get_response_data( true ), $status );
}
return new Response( [ 'message' => $exception->getMessage() ], $status );
}
}
Controllers/RestAPI/AuthController.php 0000644 00000014217 15155033414 0014005 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\RestAPI;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\OAuthService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\AccountService;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Request as Request;
defined( 'ABSPATH' ) || exit;
/**
* Class AuthController
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\RestAPI
*
* @since 2.8.0
*/
class AuthController extends BaseController {
/**
* @var OAuthService
*/
protected $oauth_service;
/**
* @var AccountService
*/
protected $account_service;
/**
* Mapping between the client page name and its path.
* The first value is also used as a default,
* and changing the order of keys/values may affect things below.
*
* @var string[]
*/
private const NEXT_PATH_MAPPING = [
'setup-mc' => '/google/setup-mc',
'settings' => '/google/settings',
];
/**
* AuthController constructor.
*
* @param RESTServer $server
* @param OAuthService $oauth_service
* @param AccountService $account_service
*/
public function __construct( RESTServer $server, OAuthService $oauth_service, AccountService $account_service ) {
parent::__construct( $server );
$this->oauth_service = $oauth_service;
$this->account_service = $account_service;
}
/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
$this->register_route(
'rest-api/authorize',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_authorize_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_auth_params(),
],
[
'methods' => TransportMethods::DELETABLE,
'callback' => $this->delete_authorize_callback(),
'permission_callback' => $this->get_permission_callback(),
],
[
'methods' => TransportMethods::EDITABLE,
'callback' => $this->get_update_authorize_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_update_authorize_params(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}
/**
* Get the callback function for the authorization request.
*
* @return callable
*/
protected function get_authorize_callback(): callable {
return function ( Request $request ) {
try {
$next = $request->get_param( 'next_page_name' );
$path = self::NEXT_PATH_MAPPING[ $next ];
$auth_url = $this->oauth_service->get_auth_url( $path );
$response = [
'auth_url' => $auth_url,
];
return $this->prepare_item_for_response( $response, $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for the delete authorization request.
*
* @return callable
*/
protected function delete_authorize_callback(): callable {
return function ( Request $request ) {
try {
$this->oauth_service->revoke_wpcom_api_auth();
return $this->prepare_item_for_response( [], $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the callback function for the update authorize request.
*
* @return callable
*/
protected function get_update_authorize_callback(): callable {
return function ( Request $request ) {
try {
$this->account_service->update_wpcom_api_authorization( $request['status'], $request['nonce'] );
return [ 'status' => $request['status'] ];
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the query params for the authorize request.
*
* @return array
*/
protected function get_auth_params(): array {
return [
'next_page_name' => [
'description' => __( 'Indicates the next page name mapped to the redirect URL when redirected back from Google WPCOM App authorization.', 'google-listings-and-ads' ),
'type' => 'string',
'default' => array_key_first( self::NEXT_PATH_MAPPING ),
'enum' => array_keys( self::NEXT_PATH_MAPPING ),
'validate_callback' => 'rest_validate_request_arg',
],
];
}
/**
* Get the query params for the update authorize request.
*
* @return array
*/
protected function get_update_authorize_params(): array {
return [
'status' => [
'description' => __( 'The status of the merchant granting access to Google\'s WPCOM app', 'google-listings-and-ads' ),
'type' => 'string',
'enum' => OAuthService::ALLOWED_STATUSES,
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
'nonce' => [
'description' => __( 'The nonce provided by Google in the URL query parameter when Google redirects back to merchant\'s site', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
];
}
/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'auth_url' => [
'type' => 'string',
'description' => __( 'The authorization URL for granting access to Google WPCOM App.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'status' => [
'type' => 'string',
'description' => __( 'The status of the merchant granting access to Google\'s WPCOM app', 'google-listings-and-ads' ),
'enum' => OAuthService::ALLOWED_STATUSES,
'context' => [ 'view' ],
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'rest_api_authorize';
}
}
Controllers/ShippingRateSchemaTrait.php 0000644 00000004706 15155033414 0014315 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\Shipping\ShippingRate;
defined( 'ABSPATH' ) || exit;
/**
* Trait ShippingRateSchemaTrait
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*
* @since 1.12.0
*/
trait ShippingRateSchemaTrait {
use CountryCodeTrait;
/**
* @return array
*/
protected function get_shipping_rate_schema(): array {
return [
'id' => [
'type' => 'number',
'description' => __( 'The shipping rate unique identification number.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'readonly' => true,
],
'country' => [
'type' => 'string',
'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'sanitize_callback' => $this->get_country_code_sanitize_callback(),
'validate_callback' => $this->get_country_code_validate_callback(),
'required' => true,
],
'currency' => [
'type' => 'string',
'description' => __( 'The currency to use for the shipping rate.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'default' => 'USD', // todo: default to store currency.
],
'rate' => [
'type' => 'number',
'minimum' => 0,
'description' => __( 'The shipping rate.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
'options' => [
'type' => 'object',
'additionalProperties' => false,
'description' => __( 'Array of options for the shipping method.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
'default' => [],
'properties' => [
'free_shipping_threshold' => [
'type' => 'number',
'minimum' => 0,
'description' => __( 'Minimum price eligible for free shipping.', 'google-listings-and-ads' ),
'context' => [ 'view', 'edit' ],
'validate_callback' => 'rest_validate_request_arg',
],
],
],
];
}
}
Controllers/TourController.php 0000644 00000011061 15155033414 0012560 0 ustar 00 <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class for handling API requests for getting and update the tour visualizations.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers
*/
class TourController extends BaseOptionsController {
/**
* Constructor.
*
* @param RESTServer $server
*/
public function __construct( RESTServer $server ) {
parent::__construct( $server );
}
/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
/**
* GET The tour visualizations
*/
$this->register_route(
"/tours/(?P<id>{$this->get_tour_id_regex()})",
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_tours_read_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
/**
* POST Update the tour visualizations
*/
$this->register_route(
'/tours',
[
[
'methods' => TransportMethods::CREATABLE,
'callback' => $this->get_tours_create_callback(),
'permission_callback' => $this->get_permission_callback(),
'args' => $this->get_schema_properties(),
],
'schema' => $this->get_api_response_schema_callback(),
],
);
}
/**
* Callback function for returning the tours
*
* @return callable
*/
protected function get_tours_read_callback(): callable {
return function ( Request $request ) {
try {
$tour_id = $request->get_url_params()['id'];
return $this->prepare_item_for_response( $this->get_tour( $tour_id ), $request );
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Callback function for saving the Tours
*
* @return callable
*/
protected function get_tours_create_callback(): callable {
return function ( Request $request ) {
try {
$tour_id = $request->get_param( 'id' );
$tours = $this->get_tours();
$tours[ $tour_id ] = $this->prepare_item_for_database( $request );
if ( $this->options->update( OptionsInterface::TOURS, $tours ) ) {
return new Response(
[
'status' => 'success',
'message' => __( 'Successfully updated the tour.', 'google-listings-and-ads' ),
],
200
);
} else {
throw new Exception( __( 'Unable to updated the tour.', 'google-listings-and-ads' ), 400 );
}
} catch ( Exception $e ) {
return $this->response_from_exception( $e );
}
};
}
/**
* Get the tours
*
* @return array|null The tours saved in databse
*/
private function get_tours(): ?array {
return $this->options->get( OptionsInterface::TOURS );
}
/**
* Get the tour by Id
*
* @param string $tour_id The tour ID
* @return array The tour
* @throws Exception In case the tour is not found.
*/
private function get_tour( string $tour_id ): array {
$tours = $this->get_tours();
if ( ! isset( $tours[ $tour_id ] ) ) {
throw new Exception( __( 'Tour not found', 'google-listings-and-ads' ), 404 );
}
return $tours[ $tour_id ];
}
/**
* Get the item schema properties for the controller.
*
* @return array The Schema properties
*/
protected function get_schema_properties(): array {
return [
'id' => [
'description' => __( 'The Id for the tour.', 'google-listings-and-ads' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
'pattern' => "^{$this->get_tour_id_regex()}$",
],
'checked' => [
'description' => __( 'Whether the tour was checked.', 'google-listings-and-ads' ),
'type' => 'boolean',
'validate_callback' => 'rest_validate_request_arg',
'required' => true,
],
];
}
/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'tours';
}
/**
* Get the regex used for the Tour ID
*
* @return string The regex
*/
private function get_tour_id_regex(): string {
return '[a-zA-z0-9-_]+';
}
}
RESTControllers.php 0000644 00000002612 15155033414 0010263 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ValidateInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Registerable;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\ContainerAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ContainerAwareInterface;
/**
* Class RESTControllers
*
* Container used for:
* - classes tagged with 'rest_controller'
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site
*/
class RESTControllers implements ContainerAwareInterface, Service, Registerable {
use ContainerAwareTrait;
use ValidateInterface;
/**
* Register a service.
*/
public function register(): void {
add_action(
'rest_api_init',
function () {
$this->register_controllers();
}
);
}
/**
* Register our individual rest controllers.
*/
protected function register_controllers(): void {
/** @var BaseController[] $controllers */
$controllers = $this->container->get( 'rest_controller' );
foreach ( $controllers as $controller ) {
$this->validate_instanceof( $controller, BaseController::class );
$controller->register();
}
}
}