Merge branch 'feature/34556-marketing-api' into feature/34903-multichannel-marketing-frontend/main
This commit is contained in:
commit
1b1d70925e
|
@ -19,7 +19,7 @@
|
|||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Database\Migrations\MigrationHelper;
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing;
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs;
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
|
||||
|
@ -2470,7 +2470,7 @@ function wc_update_700_remove_download_log_fk() {
|
|||
* Remove the transient data for recommended marketing extensions.
|
||||
*/
|
||||
function wc_update_700_remove_recommended_marketing_plugins_transient() {
|
||||
delete_transient( Marketing::RECOMMENDED_PLUGINS_TRANSIENT );
|
||||
delete_transient( MarketingSpecs::RECOMMENDED_PLUGINS_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -64,6 +64,10 @@ class Init {
|
|||
'Automattic\WooCommerce\Admin\API\Experiments',
|
||||
'Automattic\WooCommerce\Admin\API\Marketing',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingOverview',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingRecommendations',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingChannels',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaigns',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaignTypes',
|
||||
'Automattic\WooCommerce\Admin\API\Options',
|
||||
'Automattic\WooCommerce\Admin\API\Orders',
|
||||
'Automattic\WooCommerce\Admin\API\PaymentGatewaySuggestions',
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing as MarketingFeature;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -103,9 +103,16 @@ class Marketing extends \WC_REST_Data_Controller {
|
|||
* @return \WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public function get_recommended_plugins( $request ) {
|
||||
/**
|
||||
* MarketingSpecs class.
|
||||
*
|
||||
* @var MarketingSpecs $marketing_specs
|
||||
*/
|
||||
$marketing_specs = wc_get_container()->get( MarketingSpecs::class );
|
||||
|
||||
// Default to marketing category (if no category set).
|
||||
$category = ( ! empty( $request->get_param( 'category' ) ) ) ? $request->get_param( 'category' ) : 'marketing';
|
||||
$all_plugins = MarketingFeature::get_instance()->get_recommended_plugins();
|
||||
$all_plugins = $marketing_specs->get_recommended_plugins();
|
||||
$valid_plugins = [];
|
||||
$per_page = $request->get_param( 'per_page' );
|
||||
|
||||
|
@ -130,7 +137,14 @@ class Marketing extends \WC_REST_Data_Controller {
|
|||
* @return \WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public function get_knowledge_base_posts( $request ) {
|
||||
/**
|
||||
* MarketingSpecs class.
|
||||
*
|
||||
* @var MarketingSpecs $marketing_specs
|
||||
*/
|
||||
$marketing_specs = wc_get_container()->get( MarketingSpecs::class );
|
||||
|
||||
$category = $request->get_param( 'category' );
|
||||
return rest_ensure_response( MarketingFeature::get_instance()->get_knowledge_base_posts( $category ) );
|
||||
return rest_ensure_response( $marketing_specs->get_knowledge_base_posts( $category ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API MarketingCampaignTypes Controller
|
||||
*
|
||||
* Handles requests to /marketing/campaign-types.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingCampaignType;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels as MarketingChannelsService;
|
||||
use WC_REST_Controller;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* MarketingCampaignTypes Controller.
|
||||
*
|
||||
* @internal
|
||||
* @extends WC_REST_Controller
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingCampaignTypes extends WC_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'marketing/campaign-types';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the collections.
|
||||
*
|
||||
* @return array Query parameters for the collection.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = parent::get_collection_params();
|
||||
unset( $params['search'] );
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given request has permission to view marketing campaigns.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an aggregated array of marketing campaigns for all active marketing channels.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
/**
|
||||
* MarketingChannels class.
|
||||
*
|
||||
* @var MarketingChannelsService $marketing_channels_service
|
||||
*/
|
||||
$marketing_channels_service = wc_get_container()->get( MarketingChannelsService::class );
|
||||
|
||||
// Aggregate the supported campaign types from all registered marketing channels.
|
||||
$responses = [];
|
||||
foreach ( $marketing_channels_service->get_registered_channels() as $channel ) {
|
||||
foreach ( $channel->get_supported_campaign_types() as $campaign_type ) {
|
||||
$response = $this->prepare_item_for_response( $campaign_type, $request );
|
||||
$responses[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $responses );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the item for the REST response.
|
||||
*
|
||||
* @param MarketingCampaignType $item WordPress representation of the item.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$data = [
|
||||
'id' => $item->get_id(),
|
||||
'name' => $item->get_name(),
|
||||
'description' => $item->get_description(),
|
||||
'channel' => [
|
||||
'slug' => $item->get_channel()->get_slug(),
|
||||
'name' => $item->get_channel()->get_name(),
|
||||
],
|
||||
'create_url' => $item->get_create_url(),
|
||||
'icon_url' => $item->get_icon_url(),
|
||||
];
|
||||
|
||||
$context = $request['context'] ?? 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'marketing_campaign_type',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'The unique identifier for the marketing campaign type.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Name of the marketing campaign type.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'description' => [
|
||||
'description' => __( 'Description of the marketing campaign type.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'channel' => [
|
||||
'description' => __( 'The marketing channel that this campaign type belongs to.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
'properties' => [
|
||||
'slug' => [
|
||||
'description' => __( 'The unique identifier of the marketing channel that this campaign type belongs to.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'The name of the marketing channel that this campaign type belongs to.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'create_url' => [
|
||||
'description' => __( 'URL to the create campaign page for this campaign type.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'icon_url' => [
|
||||
'description' => __( 'URL to an image/icon for the campaign type.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API MarketingCampaigns Controller
|
||||
*
|
||||
* Handles requests to /marketing/campaigns.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingCampaign;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels as MarketingChannelsService;
|
||||
use Automattic\WooCommerce\Admin\Marketing\Price;
|
||||
use WC_REST_Controller;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* MarketingCampaigns Controller.
|
||||
*
|
||||
* @internal
|
||||
* @extends WC_REST_Controller
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingCampaigns extends WC_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'marketing/campaigns';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given request has permission to view marketing campaigns.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an aggregated array of marketing campaigns for all active marketing channels.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
/**
|
||||
* MarketingChannels class.
|
||||
*
|
||||
* @var MarketingChannelsService $marketing_channels_service
|
||||
*/
|
||||
$marketing_channels_service = wc_get_container()->get( MarketingChannelsService::class );
|
||||
|
||||
// Aggregate the campaigns from all registered marketing channels.
|
||||
$responses = [];
|
||||
foreach ( $marketing_channels_service->get_registered_channels() as $channel ) {
|
||||
foreach ( $channel->get_campaigns() as $campaign ) {
|
||||
$response = $this->prepare_item_for_response( $campaign, $request );
|
||||
$responses[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination.
|
||||
$page = $request['page'];
|
||||
$items_per_page = $request['per_page'];
|
||||
$offset = ( $page - 1 ) * $items_per_page;
|
||||
$paginated_results = array_slice( $responses, $offset, $items_per_page );
|
||||
|
||||
$response = rest_ensure_response( $paginated_results );
|
||||
|
||||
$total_campaigns = count( $responses );
|
||||
$max_pages = ceil( $total_campaigns / $items_per_page );
|
||||
$response->header( 'X-WP-Total', $total_campaigns );
|
||||
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
||||
|
||||
// Add previous and next page links to response header.
|
||||
$request_params = $request->get_query_params();
|
||||
$base = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||
if ( $page > 1 ) {
|
||||
$prev_page = $page - 1;
|
||||
if ( $prev_page > $max_pages ) {
|
||||
$prev_page = $max_pages;
|
||||
}
|
||||
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||
$response->link_header( 'prev', $prev_link );
|
||||
}
|
||||
if ( $max_pages > $page ) {
|
||||
$next_page = $page + 1;
|
||||
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||
$response->link_header( 'next', $next_link );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the item for the REST response.
|
||||
*
|
||||
* @param MarketingCampaign $item WordPress representation of the item.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$data = [
|
||||
'id' => $item->get_id(),
|
||||
'channel' => $item->get_type()->get_channel()->get_slug(),
|
||||
'title' => $item->get_title(),
|
||||
'manage_url' => $item->get_manage_url(),
|
||||
];
|
||||
|
||||
if ( $item->get_cost() instanceof Price ) {
|
||||
$data['cost'] = [
|
||||
'value' => wc_format_decimal( $item->get_cost()->get_value() ),
|
||||
'currency' => $item->get_cost()->get_currency(),
|
||||
];
|
||||
}
|
||||
|
||||
$context = $request['context'] ?? 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'marketing_campaign',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'The unique identifier for the marketing campaign.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'channel' => [
|
||||
'description' => __( 'The unique identifier for the marketing channel that this campaign belongs to.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'title' => [
|
||||
'description' => __( 'Title of the marketing campaign.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'manage_url' => [
|
||||
'description' => __( 'URL to the campaign management page.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'cost' => [
|
||||
'description' => __( 'Cost of the marketing campaign.', 'woocommerce' ),
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'value' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'currency' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the collections.
|
||||
*
|
||||
* @return array Query parameters for the collection.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = parent::get_collection_params();
|
||||
unset( $params['search'] );
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API MarketingChannels Controller
|
||||
*
|
||||
* Handles requests to /marketing/channels.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannelInterface;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels as MarketingChannelsService;
|
||||
use WC_REST_Controller;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* MarketingChannels Controller.
|
||||
*
|
||||
* @internal
|
||||
* @extends WC_REST_Controller
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingChannels extends WC_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'marketing/channels';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given request has permission to view marketing channels.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return installed marketing channels.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
/**
|
||||
* MarketingChannels class.
|
||||
*
|
||||
* @var MarketingChannelsService $marketing_channels_service
|
||||
*/
|
||||
$marketing_channels_service = wc_get_container()->get( MarketingChannelsService::class );
|
||||
|
||||
$channels = $marketing_channels_service->get_registered_channels();
|
||||
|
||||
$responses = [];
|
||||
foreach ( $channels as $item ) {
|
||||
$response = $this->prepare_item_for_response( $item, $request );
|
||||
$responses[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $responses );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the item for the REST response.
|
||||
*
|
||||
* @param MarketingChannelInterface $item WordPress representation of the item.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$data = [
|
||||
'slug' => $item->get_slug(),
|
||||
'is_setup_completed' => $item->is_setup_completed(),
|
||||
'settings_url' => $item->get_setup_url(),
|
||||
'name' => $item->get_name(),
|
||||
'description' => $item->get_description(),
|
||||
'product_listings_status' => $item->get_product_listings_status(),
|
||||
'errors_count' => $item->get_errors_count(),
|
||||
'icon' => $item->get_icon_url(),
|
||||
];
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'marketing_channel',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'slug' => [
|
||||
'description' => __( 'Unique identifier string for the marketing channel extension, also known as the plugin slug.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Name of the marketing channel.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'description' => [
|
||||
'description' => __( 'Description of the marketing channel.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'icon' => [
|
||||
'description' => __( 'Path to the channel icon.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'is_setup_completed' => [
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether or not the marketing channel is set up.', 'woocommerce' ),
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'settings_url' => [
|
||||
'description' => __( 'URL to the settings page, or the link to complete the setup/onboarding if the channel has not been set up yet.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'product_listings_status' => [
|
||||
'description' => __( 'Status of the marketing channel\'s product listings.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'errors_count' => [
|
||||
'description' => __( 'Number of channel issues/errors (e.g. account-related errors, product synchronization issues, etc.).', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API MarketingRecommendations Controller
|
||||
*
|
||||
* Handles requests to /marketing/recommendations.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs;
|
||||
use WC_REST_Controller;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* MarketingRecommendations Controller.
|
||||
*
|
||||
* @internal
|
||||
* @extends WC_REST_Controller
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingRecommendations extends WC_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'marketing/recommendations';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_items' ],
|
||||
'permission_callback' => [ $this, 'get_items_permissions_check' ],
|
||||
'args' => [
|
||||
'category' => [
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'sanitize_callback' => 'sanitize_title_with_dashes',
|
||||
'enum' => [ 'channels', 'extensions' ],
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this, 'get_public_item_schema' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given request has permission to view marketing recommendations.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view marketing channels.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of recommendations.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
/**
|
||||
* MarketingSpecs class.
|
||||
*
|
||||
* @var MarketingSpecs $marketing_specs
|
||||
*/
|
||||
$marketing_specs = wc_get_container()->get( MarketingSpecs::class );
|
||||
|
||||
$category = $request->get_param( 'category' );
|
||||
if ( 'channels' === $category ) {
|
||||
$items = $marketing_specs->get_recommended_marketing_channels();
|
||||
} elseif ( 'extensions' === $category ) {
|
||||
$items = $marketing_specs->get_recommended_marketing_extensions_excluding_channels();
|
||||
} else {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_category', __( 'The specified category for recommendations is invalid. Allowed values: "channels", "extensions".', 'woocommerce' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$responses = [];
|
||||
foreach ( $items as $item ) {
|
||||
$response = $this->prepare_item_for_response( $item, $request );
|
||||
$responses[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $responses );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the item for the REST response.
|
||||
*
|
||||
* @param array $item WordPress representation of the item.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $item, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'marketing_recommendation',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'title' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'description' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'url' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'direct_install' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'icon' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'product' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'plugin' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'categories' => [
|
||||
'type' => 'array',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
'subcategories' => [
|
||||
'type' => 'array',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
'properties' => [
|
||||
'slug' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'tags' => [
|
||||
'type' => 'array',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
'properties' => [
|
||||
'slug' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
}
|
|
@ -241,7 +241,7 @@ class InstalledExtensions {
|
|||
$data = self::get_extension_base_data( $slug );
|
||||
$data['icon'] = WC_ADMIN_IMAGES_FOLDER_URL . '/marketing/facebook-icon.svg';
|
||||
|
||||
if ( $data['status'] === 'activated' && function_exists( 'facebook_for_woocommerce' ) ) {
|
||||
if ( 'activated' === $data['status'] && function_exists( 'facebook_for_woocommerce' ) ) {
|
||||
$integration = facebook_for_woocommerce()->get_integration();
|
||||
|
||||
if ( $integration->is_configured() ) {
|
||||
|
@ -270,7 +270,6 @@ class InstalledExtensions {
|
|||
$data = self::get_extension_base_data( $slug );
|
||||
$data['icon'] = WC_ADMIN_IMAGES_FOLDER_URL . '/marketing/pinterest.svg';
|
||||
|
||||
// TODO: Finalise docs url.
|
||||
$data['docsUrl'] = 'https://woocommerce.com/document/pinterest-for-woocommerce/?utm_medium=product';
|
||||
|
||||
if ( 'activated' === $data['status'] && class_exists( 'Pinterest_For_Woocommerce' ) ) {
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
/**
|
||||
* Represents a marketing/ads campaign for marketing channels.
|
||||
*
|
||||
* Marketing channels (implementing MarketingChannelInterface) can use this class to map their campaign data and present it to WooCommerce core.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Marketing;
|
||||
|
||||
/**
|
||||
* MarketingCampaign class
|
||||
*
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingCampaign {
|
||||
/**
|
||||
* The unique identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The marketing campaign type.
|
||||
*
|
||||
* @var MarketingCampaignType
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Title of the marketing campaign.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* The URL to the channel's campaign management page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $manage_url;
|
||||
|
||||
/**
|
||||
* The cost of the marketing campaign with the currency.
|
||||
*
|
||||
* @var Price
|
||||
*/
|
||||
protected $cost;
|
||||
|
||||
/**
|
||||
* MarketingCampaign constructor.
|
||||
*
|
||||
* @param string $id The marketing campaign's unique identifier.
|
||||
* @param MarketingCampaignType $type The marketing campaign type.
|
||||
* @param string $title The title of the marketing campaign.
|
||||
* @param string $manage_url The URL to the channel's campaign management page.
|
||||
* @param Price|null $cost The cost of the marketing campaign with the currency.
|
||||
*/
|
||||
public function __construct( string $id, MarketingCampaignType $type, string $title, string $manage_url, Price $cost = null ) {
|
||||
$this->id = $id;
|
||||
$this->type = $type;
|
||||
$this->title = $title;
|
||||
$this->manage_url = $manage_url;
|
||||
$this->cost = $cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the marketing campaign's unique identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the marketing campaign type.
|
||||
*
|
||||
* @return MarketingCampaignType
|
||||
*/
|
||||
public function get_type(): MarketingCampaignType {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title of the marketing campaign.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to manage the marketing campaign.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_manage_url(): string {
|
||||
return $this->manage_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cost of the marketing campaign with the currency.
|
||||
*
|
||||
* @return Price|null
|
||||
*/
|
||||
public function get_cost(): ?Price {
|
||||
return $this->cost;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
/**
|
||||
* Represents a marketing campaign type supported by a marketing channel.
|
||||
*
|
||||
* Marketing channels (implementing MarketingChannelInterface) can use this class to define what kind of campaigns they support.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Marketing;
|
||||
|
||||
/**
|
||||
* MarketingCampaignType class
|
||||
*
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingCampaignType {
|
||||
/**
|
||||
* The unique identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The marketing channel that this campaign type belongs to.
|
||||
*
|
||||
* @var MarketingChannelInterface
|
||||
*/
|
||||
protected $channel;
|
||||
|
||||
/**
|
||||
* Name of the marketing campaign type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Description of the marketing campaign type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* The URL to the create campaign page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $create_url;
|
||||
|
||||
/**
|
||||
* The URL to an image/icon for the campaign type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $icon_url;
|
||||
|
||||
/**
|
||||
* MarketingCampaignType constructor.
|
||||
*
|
||||
* @param string $id A unique identifier for the campaign type.
|
||||
* @param MarketingChannelInterface $channel The marketing channel that this campaign type belongs to.
|
||||
* @param string $name Name of the marketing campaign type.
|
||||
* @param string $description Description of the marketing campaign type.
|
||||
* @param string $create_url The URL to the create campaign page.
|
||||
* @param string $icon_url The URL to an image/icon for the campaign type.
|
||||
*/
|
||||
public function __construct( string $id, MarketingChannelInterface $channel, string $name, string $description, string $create_url, string $icon_url ) {
|
||||
$this->id = $id;
|
||||
$this->channel = $channel;
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
$this->create_url = $create_url;
|
||||
$this->icon_url = $icon_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the marketing campaign's unique identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the marketing channel that this campaign type belongs to.
|
||||
*
|
||||
* @return MarketingChannelInterface
|
||||
*/
|
||||
public function get_channel(): MarketingChannelInterface {
|
||||
return $this->channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the marketing campaign type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the marketing campaign type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description(): string {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to the create campaign page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_create_url(): string {
|
||||
return $this->create_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to an image/icon for the campaign type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_icon_url(): string {
|
||||
return $this->icon_url;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/**
|
||||
* Represents a marketing channel for the multichannel-marketing feature.
|
||||
*
|
||||
* This interface will be implemented by third-party extensions to register themselves as marketing channels.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Marketing;
|
||||
|
||||
/**
|
||||
* MarketingChannelInterface interface
|
||||
*
|
||||
* @since x.x.x
|
||||
*/
|
||||
interface MarketingChannelInterface {
|
||||
public const PRODUCT_LISTINGS_NOT_APPLICABLE = 'not-applicable';
|
||||
public const PRODUCT_LISTINGS_SYNC_IN_PROGRESS = 'sync-in-progress';
|
||||
public const PRODUCT_LISTINGS_SYNCED = 'synced';
|
||||
|
||||
/**
|
||||
* Returns the unique identifier string for the marketing channel extension, also known as the plugin slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_slug(): string;
|
||||
|
||||
/**
|
||||
* Returns the name of the marketing channel.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string;
|
||||
|
||||
/**
|
||||
* Returns the description of the marketing channel.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description(): string;
|
||||
|
||||
/**
|
||||
* Returns the path to the channel icon.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_icon_url(): string;
|
||||
|
||||
/**
|
||||
* Returns the setup status of the marketing channel.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_setup_completed(): bool;
|
||||
|
||||
/**
|
||||
* Returns the URL to the settings page, or the link to complete the setup/onboarding if the channel has not been set up yet.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_setup_url(): string;
|
||||
|
||||
/**
|
||||
* Returns the status of the marketing channel's product listings.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_product_listings_status(): string;
|
||||
|
||||
/**
|
||||
* Returns the number of channel issues/errors (e.g. account-related errors, product synchronization issues, etc.).
|
||||
*
|
||||
* @return int The number of issues to resolve, or 0 if there are no issues with the channel.
|
||||
*/
|
||||
public function get_errors_count(): int;
|
||||
|
||||
/**
|
||||
* Returns an array of marketing campaign types that the channel supports.
|
||||
*
|
||||
* @return MarketingCampaignType[] Array of marketing campaign type objects.
|
||||
*/
|
||||
public function get_supported_campaign_types(): array;
|
||||
|
||||
/**
|
||||
* Returns an array of the channel's marketing campaigns.
|
||||
*
|
||||
* @return MarketingCampaign[]
|
||||
*/
|
||||
public function get_campaigns(): array;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles the registration of marketing channels and acts as their repository.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Marketing;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* MarketingChannels repository class
|
||||
*
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingChannels {
|
||||
/**
|
||||
* The registered marketing channels.
|
||||
*
|
||||
* @var MarketingChannelInterface[]
|
||||
*/
|
||||
private $registered_channels = [];
|
||||
|
||||
/**
|
||||
* Registers a marketing channel.
|
||||
*
|
||||
* @param MarketingChannelInterface $channel The marketing channel to register.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If the given marketing channel is already registered.
|
||||
*/
|
||||
public function register( MarketingChannelInterface $channel ): void {
|
||||
if ( isset( $this->registered_channels[ $channel->get_slug() ] ) ) {
|
||||
throw new Exception( __( 'Marketing channel cannot be registered because there is already a channel registered with the same slug!', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$this->registered_channels[ $channel->get_slug() ] = $channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all marketing channels.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister_all(): void {
|
||||
unset( $this->registered_channels );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all registered marketing channels.
|
||||
*
|
||||
* @return MarketingChannelInterface[]
|
||||
*/
|
||||
public function get_registered_channels(): array {
|
||||
/**
|
||||
* Filter the list of registered marketing channels.
|
||||
*
|
||||
* @param MarketingChannelInterface[] $channels Array of registered marketing channels.
|
||||
*
|
||||
* @since x.x.x
|
||||
*/
|
||||
$channels = apply_filters( 'woocommerce_marketing_channels', $this->registered_channels );
|
||||
|
||||
return array_values( $channels );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* Represents a price with a currency.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Marketing;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* Price class
|
||||
*
|
||||
* @since x.x.x
|
||||
*/
|
||||
class Price implements JsonSerializable {
|
||||
/**
|
||||
* The price.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* The currency of the price.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $currency;
|
||||
|
||||
/**
|
||||
* Price constructor.
|
||||
*
|
||||
* @param string $value The value of the price.
|
||||
* @param string $currency The currency of the price.
|
||||
*/
|
||||
public function __construct( string $value, string $currency ) {
|
||||
$this->value = $value;
|
||||
$this->currency = $currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value of the price.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_value(): string {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currency of the price.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_currency(): string {
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the price data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'value' => $this->get_value(),
|
||||
'currency' => $this->get_currency(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\COTMig
|
|||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\AssignDefaultCategoryServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\FeaturesServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\MarketingServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrdersControllersServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrderAdminServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrderMetaBoxServiceProvider;
|
||||
|
@ -65,6 +66,7 @@ final class Container {
|
|||
OrderMetaBoxServiceProvider::class,
|
||||
OrderAdminServiceProvider::class,
|
||||
FeaturesServiceProvider::class,
|
||||
MarketingServiceProvider::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace Automattic\WooCommerce\Internal\Admin;
|
|||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Marketing\InstalledExtensions;
|
||||
use Automattic\WooCommerce\Internal\Admin\Loader;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
|
||||
/**
|
||||
|
@ -17,20 +16,6 @@ class Marketing {
|
|||
|
||||
use CouponsMovedTrait;
|
||||
|
||||
/**
|
||||
* Name of recommended plugins transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RECOMMENDED_PLUGINS_TRANSIENT = 'wc_marketing_recommended_plugins';
|
||||
|
||||
/**
|
||||
* Name of knowledge base post transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KNOWLEDGE_BASE_TRANSIENT = 'wc_marketing_knowledge_base';
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
|
@ -184,120 +169,4 @@ class Marketing {
|
|||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load recommended plugins from WooCommerce.com
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_recommended_plugins() {
|
||||
$plugins = get_transient( self::RECOMMENDED_PLUGINS_TRANSIENT );
|
||||
|
||||
if ( false === $plugins ) {
|
||||
$request = wp_remote_get(
|
||||
'https://woocommerce.com/wp-json/wccom/marketing-tab/1.2/recommendations.json',
|
||||
array(
|
||||
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
|
||||
)
|
||||
);
|
||||
$plugins = [];
|
||||
|
||||
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
|
||||
$plugins = json_decode( $request['body'], true );
|
||||
}
|
||||
|
||||
set_transient(
|
||||
self::RECOMMENDED_PLUGINS_TRANSIENT,
|
||||
$plugins,
|
||||
// Expire transient in 15 minutes if remote get failed.
|
||||
// Cache an empty result to avoid repeated failed requests.
|
||||
empty( $plugins ) ? 900 : 3 * DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
return array_values( $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load knowledge base posts from WooCommerce.com
|
||||
*
|
||||
* @param string $category Category of posts to retrieve.
|
||||
* @return array
|
||||
*/
|
||||
public function get_knowledge_base_posts( $category ) {
|
||||
|
||||
$kb_transient = self::KNOWLEDGE_BASE_TRANSIENT;
|
||||
|
||||
$categories = array(
|
||||
'marketing' => 1744,
|
||||
'coupons' => 25202,
|
||||
);
|
||||
|
||||
// Default to marketing category (if no category set on the kb component).
|
||||
if ( ! empty( $category ) && array_key_exists( $category, $categories ) ) {
|
||||
$category_id = $categories[ $category ];
|
||||
$kb_transient = $kb_transient . '_' . strtolower( $category );
|
||||
} else {
|
||||
$category_id = $categories['marketing'];
|
||||
}
|
||||
|
||||
$posts = get_transient( $kb_transient );
|
||||
|
||||
if ( false === $posts ) {
|
||||
$request_url = add_query_arg(
|
||||
array(
|
||||
'categories' => $category_id,
|
||||
'page' => 1,
|
||||
'per_page' => 8,
|
||||
'_embed' => 1,
|
||||
),
|
||||
'https://woocommerce.com/wp-json/wp/v2/posts?utm_medium=product'
|
||||
);
|
||||
|
||||
$request = wp_remote_get(
|
||||
$request_url,
|
||||
array(
|
||||
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
|
||||
)
|
||||
);
|
||||
$posts = [];
|
||||
|
||||
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
|
||||
$raw_posts = json_decode( $request['body'], true );
|
||||
|
||||
foreach ( $raw_posts as $raw_post ) {
|
||||
$post = [
|
||||
'title' => html_entity_decode( $raw_post['title']['rendered'] ),
|
||||
'date' => $raw_post['date_gmt'],
|
||||
'link' => $raw_post['link'],
|
||||
'author_name' => isset( $raw_post['author_name'] ) ? html_entity_decode( $raw_post['author_name'] ) : '',
|
||||
'author_avatar' => isset( $raw_post['author_avatar_url'] ) ? $raw_post['author_avatar_url'] : '',
|
||||
];
|
||||
|
||||
$featured_media = $raw_post['_embedded']['wp:featuredmedia'] ?? [];
|
||||
if ( count( $featured_media ) > 0 ) {
|
||||
$image = current( $featured_media );
|
||||
$post['image'] = add_query_arg(
|
||||
array(
|
||||
'resize' => '650,340',
|
||||
'crop' => 1,
|
||||
),
|
||||
$image['source_url']
|
||||
);
|
||||
}
|
||||
|
||||
$posts[] = $post;
|
||||
}
|
||||
}
|
||||
|
||||
set_transient(
|
||||
$kb_transient,
|
||||
$posts,
|
||||
// Expire transient in 15 minutes if remote get failed.
|
||||
empty( $posts ) ? 900 : DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
/**
|
||||
* Marketing Specs Handler
|
||||
*
|
||||
* Fetches the specifications for the marketing feature from WC.com API.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Marketing;
|
||||
|
||||
/**
|
||||
* Marketing Specifications Class.
|
||||
*
|
||||
* @internal
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingSpecs {
|
||||
/**
|
||||
* Name of recommended plugins transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RECOMMENDED_PLUGINS_TRANSIENT = 'wc_marketing_recommended_plugins';
|
||||
|
||||
/**
|
||||
* Name of knowledge base post transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KNOWLEDGE_BASE_TRANSIENT = 'wc_marketing_knowledge_base';
|
||||
|
||||
/**
|
||||
* Slug of the category specifying marketing extensions on the WooCommerce.com store.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MARKETING_EXTENSION_CATEGORY_SLUG = 'marketing';
|
||||
|
||||
/**
|
||||
* Slug of the subcategory specifying marketing channels on the WooCommerce.com store.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MARKETING_CHANNEL_SUBCATEGORY_SLUG = 'sales-channels';
|
||||
|
||||
/**
|
||||
* Load recommended plugins from WooCommerce.com
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_recommended_plugins(): array {
|
||||
$plugins = get_transient( self::RECOMMENDED_PLUGINS_TRANSIENT );
|
||||
|
||||
if ( false === $plugins ) {
|
||||
$request = wp_remote_get(
|
||||
'https://woocommerce.com/wp-json/wccom/marketing-tab/1.2/recommendations.json',
|
||||
array(
|
||||
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
|
||||
)
|
||||
);
|
||||
$plugins = [];
|
||||
|
||||
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
|
||||
$plugins = json_decode( $request['body'], true );
|
||||
}
|
||||
|
||||
set_transient(
|
||||
self::RECOMMENDED_PLUGINS_TRANSIENT,
|
||||
$plugins,
|
||||
// Expire transient in 15 minutes if remote get failed.
|
||||
// Cache an empty result to avoid repeated failed requests.
|
||||
empty( $plugins ) ? 900 : 3 * DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
return array_values( $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only the recommended marketing channels from WooCommerce.com.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_recommended_marketing_channels(): array {
|
||||
return array_filter( $this->get_recommended_plugins(), [ $this, 'is_marketing_channel_plugin' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all recommended marketing extensions EXCEPT the marketing channels from WooCommerce.com.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_recommended_marketing_extensions_excluding_channels(): array {
|
||||
return array_filter(
|
||||
$this->get_recommended_plugins(),
|
||||
function ( array $plugin_data ) {
|
||||
return $this->is_marketing_plugin( $plugin_data ) && ! $this->is_marketing_channel_plugin( $plugin_data );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a plugin is a marketing extension.
|
||||
*
|
||||
* @param array $plugin_data The plugin properties returned by the API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_marketing_plugin( array $plugin_data ): bool {
|
||||
$categories = $plugin_data['categories'] ?? [];
|
||||
|
||||
return in_array( self::MARKETING_EXTENSION_CATEGORY_SLUG, $categories, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a plugin is a marketing channel.
|
||||
*
|
||||
* @param array $plugin_data The plugin properties returned by the API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_marketing_channel_plugin( array $plugin_data ): bool {
|
||||
if ( ! $this->is_marketing_plugin( $plugin_data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subcategories = $plugin_data['subcategories'] ?? [];
|
||||
foreach ( $subcategories as $subcategory ) {
|
||||
if ( isset( $subcategory['slug'] ) && self::MARKETING_CHANNEL_SUBCATEGORY_SLUG === $subcategory['slug'] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load knowledge base posts from WooCommerce.com
|
||||
*
|
||||
* @param string|null $category Category of posts to retrieve.
|
||||
* @return array
|
||||
*/
|
||||
public function get_knowledge_base_posts( ?string $category ): array {
|
||||
$kb_transient = self::KNOWLEDGE_BASE_TRANSIENT;
|
||||
|
||||
$categories = array(
|
||||
'marketing' => 1744,
|
||||
'coupons' => 25202,
|
||||
);
|
||||
|
||||
// Default to marketing category (if no category set on the kb component).
|
||||
if ( ! empty( $category ) && array_key_exists( $category, $categories ) ) {
|
||||
$category_id = $categories[ $category ];
|
||||
$kb_transient = $kb_transient . '_' . strtolower( $category );
|
||||
} else {
|
||||
$category_id = $categories['marketing'];
|
||||
}
|
||||
|
||||
$posts = get_transient( $kb_transient );
|
||||
|
||||
if ( false === $posts ) {
|
||||
$request_url = add_query_arg(
|
||||
array(
|
||||
'categories' => $category_id,
|
||||
'page' => 1,
|
||||
'per_page' => 8,
|
||||
'_embed' => 1,
|
||||
),
|
||||
'https://woocommerce.com/wp-json/wp/v2/posts?utm_medium=product'
|
||||
);
|
||||
|
||||
$request = wp_remote_get(
|
||||
$request_url,
|
||||
array(
|
||||
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
|
||||
)
|
||||
);
|
||||
$posts = [];
|
||||
|
||||
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
|
||||
$raw_posts = json_decode( $request['body'], true );
|
||||
|
||||
foreach ( $raw_posts as $raw_post ) {
|
||||
$post = [
|
||||
'title' => html_entity_decode( $raw_post['title']['rendered'] ),
|
||||
'date' => $raw_post['date_gmt'],
|
||||
'link' => $raw_post['link'],
|
||||
'author_name' => isset( $raw_post['author_name'] ) ? html_entity_decode( $raw_post['author_name'] ) : '',
|
||||
'author_avatar' => isset( $raw_post['author_avatar_url'] ) ? $raw_post['author_avatar_url'] : '',
|
||||
];
|
||||
|
||||
$featured_media = $raw_post['_embedded']['wp:featuredmedia'] ?? [];
|
||||
if ( count( $featured_media ) > 0 ) {
|
||||
$image = current( $featured_media );
|
||||
$post['image'] = add_query_arg(
|
||||
array(
|
||||
'resize' => '650,340',
|
||||
'crop' => 1,
|
||||
),
|
||||
$image['source_url']
|
||||
);
|
||||
}
|
||||
|
||||
$posts[] = $post;
|
||||
}
|
||||
}
|
||||
|
||||
set_transient(
|
||||
$kb_transient,
|
||||
$posts,
|
||||
// Expire transient in 15 minutes if remote get failed.
|
||||
empty( $posts ) ? 900 : DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
/**
|
||||
* MarketingServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels;
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
|
||||
// Indicates that the multichannel marketing classes exist.
|
||||
// This constant will be checked by third-party extensions before utilizing any of the classes defined for this feature.
|
||||
if ( ! defined( 'WC_MCM_EXISTS' ) ) {
|
||||
define( 'WC_MCM_EXISTS', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Service provider for the non-static utils classes in the Automattic\WooCommerce\src namespace.
|
||||
*
|
||||
* @since x.x.x
|
||||
*/
|
||||
class MarketingServiceProvider extends AbstractServiceProvider {
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
MarketingSpecs::class,
|
||||
MarketingChannels::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
$this->share( MarketingSpecs::class );
|
||||
$this->share( MarketingChannels::class );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
/**
|
||||
* Test the API controller class that handles the marketing campaign types REST response.
|
||||
*
|
||||
* @package WooCommerce\Admin\Tests\Admin\API
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingCampaignType;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannelInterface;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels as MarketingChannelsService;
|
||||
use WC_REST_Unit_Test_Case;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* MarketingCampaigns API controller test.
|
||||
*
|
||||
* @class MarketingCampaignTypesTest.
|
||||
*/
|
||||
class MarketingCampaignTypesTest extends WC_REST_Unit_Test_Case {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENDPOINT = '/wc-admin/marketing/campaign-types';
|
||||
|
||||
/**
|
||||
* @var MarketingChannelsService
|
||||
*/
|
||||
private $marketing_channels_service;
|
||||
|
||||
/**
|
||||
* Set up.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Register an administrator user and log in.
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$this->marketing_channels_service = wc_get_container()->get( MarketingChannelsService::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test teardown.
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
$this->marketing_channels_service->unregister_all();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the marketing campaigns for all registered channels are aggregated and returned by the endpoint.
|
||||
*/
|
||||
public function test_returns_aggregated_marketing_campaigns() {
|
||||
// Create a mock marketing channel.
|
||||
$test_channel_1 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
// Create a mock marketing campaign type.
|
||||
$test_campaign_type_1 = $this->createMock( MarketingCampaignType::class );
|
||||
$test_campaign_type_1->expects( $this->any() )->method( 'get_id' )->willReturn( 'test-campaign-type-1' );
|
||||
$test_campaign_type_1->expects( $this->any() )->method( 'get_channel' )->willReturn( $test_channel_1 );
|
||||
// Return the sample campaign type by the mock marketing channel.
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_supported_campaign_types' )->willReturn( [ $test_campaign_type_1 ] );
|
||||
// Register the marketing channel.
|
||||
$this->marketing_channels_service->register( $test_channel_1 );
|
||||
|
||||
// Create a second mock marketing channel.
|
||||
$test_channel_2 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_2->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-2' );
|
||||
// Create a mock marketing campaign type for the second marketing channel.
|
||||
$test_campaign_type_2 = $this->createMock( MarketingCampaignType::class );
|
||||
$test_campaign_type_2->expects( $this->any() )->method( 'get_id' )->willReturn( 'test-campaign-type-2' );
|
||||
$test_campaign_type_2->expects( $this->any() )->method( 'get_channel' )->willReturn( $test_channel_2 );
|
||||
// Return the sample campaign by the second mock marketing channel.
|
||||
$test_channel_2->expects( $this->any() )->method( 'get_supported_campaign_types' )->willReturn( [ $test_campaign_type_2 ] );
|
||||
// Register the second marketing channel.
|
||||
$this->marketing_channels_service->register( $test_channel_2 );
|
||||
|
||||
$request = new WP_REST_Request( 'GET', self::ENDPOINT );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertCount( 2, $data );
|
||||
$this->assertEquals(
|
||||
[
|
||||
'test-campaign-type-1',
|
||||
'test-campaign-type-2',
|
||||
],
|
||||
array_column( $data, 'id' )
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
/**
|
||||
* Test the API controller class that handles the marketing campaigns REST response.
|
||||
*
|
||||
* @package WooCommerce\Admin\Tests\Admin\API
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingCampaign;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannelInterface;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels as MarketingChannelsService;
|
||||
use WC_REST_Unit_Test_Case;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* MarketingCampaigns API controller test.
|
||||
*
|
||||
* @class MarketingCampaignsTest.
|
||||
*/
|
||||
class MarketingCampaignsTest extends WC_REST_Unit_Test_Case {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENDPOINT = '/wc-admin/marketing/campaigns';
|
||||
|
||||
/**
|
||||
* @var MarketingChannelsService
|
||||
*/
|
||||
private $marketing_channels_service;
|
||||
|
||||
/**
|
||||
* Set up.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Register an administrator user and log in.
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$this->marketing_channels_service = wc_get_container()->get( MarketingChannelsService::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test teardown.
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
$this->marketing_channels_service->unregister_all();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the marketing campaigns for all registered channels are aggregated and returned by the endpoint.
|
||||
*/
|
||||
public function test_returns_aggregated_marketing_campaigns() {
|
||||
// Create a mock marketing channel.
|
||||
$test_channel_1 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
// Create a mock marketing campaign.
|
||||
$test_campaign_1 = $this->createMock( MarketingCampaign::class );
|
||||
$test_campaign_1->expects( $this->any() )->method( 'get_id' )->willReturn( 'test-campaign-1' );
|
||||
$test_campaign_1->expects( $this->any() )->method( 'get_channel' )->willReturn( $test_channel_1 );
|
||||
// Return the sample campaign by the mock marketing channel.
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_campaigns' )->willReturn( [ $test_campaign_1 ] );
|
||||
// Register the marketing channel.
|
||||
$this->marketing_channels_service->register( $test_channel_1 );
|
||||
|
||||
// Create a second mock marketing channel.
|
||||
$test_channel_2 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_2->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-2' );
|
||||
// Create a mock marketing campaign for the second marketing channel.
|
||||
$test_campaign_2 = $this->createMock( MarketingCampaign::class );
|
||||
$test_campaign_2->expects( $this->any() )->method( 'get_id' )->willReturn( 'test-campaign-2' );
|
||||
$test_campaign_2->expects( $this->any() )->method( 'get_channel' )->willReturn( $test_channel_2 );
|
||||
// Return the sample campaign by the second mock marketing channel.
|
||||
$test_channel_2->expects( $this->any() )->method( 'get_campaigns' )->willReturn( [ $test_campaign_2 ] );
|
||||
// Register the second marketing channel.
|
||||
$this->marketing_channels_service->register( $test_channel_2 );
|
||||
|
||||
$request = new WP_REST_Request( 'GET', self::ENDPOINT );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertCount( 2, $data );
|
||||
$this->assertEquals(
|
||||
[
|
||||
'test-campaign-1',
|
||||
'test-campaign-2',
|
||||
],
|
||||
array_column( $data, 'id' )
|
||||
);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'test-channel-1',
|
||||
'test-channel-2',
|
||||
],
|
||||
array_column( $data, 'channel' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the marketing campaigns are paginated and then returned by the endpoint.
|
||||
*/
|
||||
public function test_paginates_marketing_campaigns() {
|
||||
// Create a mock marketing channel.
|
||||
$test_channel_1 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
// Return mock campaigns by the mock marketing channel.
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_campaigns' )->willReturn(
|
||||
[
|
||||
$this->createMock( MarketingCampaign::class ),
|
||||
$this->createMock( MarketingCampaign::class ),
|
||||
$this->createMock( MarketingCampaign::class ),
|
||||
$this->createMock( MarketingCampaign::class ),
|
||||
$this->createMock( MarketingCampaign::class ),
|
||||
]
|
||||
);
|
||||
// Register the marketing channel.
|
||||
$this->marketing_channels_service->register( $test_channel_1 );
|
||||
|
||||
$endpoint = self::ENDPOINT;
|
||||
$request = new WP_REST_Request( 'GET', $endpoint );
|
||||
$request->set_query_params(
|
||||
[
|
||||
'page' => '1',
|
||||
'per_page' => '2',
|
||||
]
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$headers = $response->get_headers();
|
||||
|
||||
$this->assertCount( 2, $response->get_data() );
|
||||
|
||||
$this->assertArrayHasKey( 'Link', $headers );
|
||||
$this->assertArrayHasKey( 'X-WP-Total', $headers );
|
||||
$this->assertArrayHasKey( 'X-WP-TotalPages', $headers );
|
||||
$this->assertEquals( 5, $headers['X-WP-Total'] );
|
||||
$this->assertEquals( 3, $headers['X-WP-TotalPages'] );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* Test the API controller class that handles the marketing channels REST response.
|
||||
*
|
||||
* @package WooCommerce\Admin\Tests\Admin\API
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannelInterface;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels as MarketingChannelsService;
|
||||
use WC_REST_Unit_Test_Case;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* MarketingChannels API controller test.
|
||||
*
|
||||
* @class MarketingChannelsTest.
|
||||
*/
|
||||
class MarketingChannelsTest extends WC_REST_Unit_Test_Case {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENDPOINT = '/wc-admin/marketing/channels';
|
||||
|
||||
/**
|
||||
* @var MarketingChannelsService
|
||||
*/
|
||||
private $marketing_channels_service;
|
||||
|
||||
/**
|
||||
* Set up.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Register an administrator user and log in.
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$this->marketing_channels_service = wc_get_container()->get( MarketingChannelsService::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test teardown.
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
$this->marketing_channels_service->unregister_all();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the registered marketing channels are returned by the endpoint.
|
||||
*/
|
||||
public function test_returns_registered_marketing_channels() {
|
||||
// Register marketing channel.
|
||||
$test_channel_1 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_name' )->willReturn( 'Test Channel One' );
|
||||
$this->marketing_channels_service->register( $test_channel_1 );
|
||||
|
||||
$request = new WP_REST_Request( 'GET', self::ENDPOINT );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertCount( 1, $data );
|
||||
$this->assertEquals( 'test-channel-1', $data[0]['slug'] );
|
||||
$this->assertEquals( 'Test Channel One', $data[0]['name'] );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
/**
|
||||
* Test the API controller class that handles the marketing recommendations REST response.
|
||||
*
|
||||
* @package WooCommerce\Admin\Tests\Admin\API
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs;
|
||||
use WC_REST_Unit_Test_Case;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* MarketingRecommendations API controller test.
|
||||
*
|
||||
* @class MarketingRecommendationsTest.
|
||||
*/
|
||||
class MarketingRecommendationsTest extends WC_REST_Unit_Test_Case {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENDPOINT = '/wc-admin/marketing/recommendations';
|
||||
|
||||
/**
|
||||
* Set up.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Register an administrator user and log in.
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
set_transient(
|
||||
MarketingSpecs::RECOMMENDED_PLUGINS_TRANSIENT,
|
||||
[
|
||||
[
|
||||
'title' => 'Example Marketing Channel',
|
||||
'description' => 'List your products and create ads, etc.',
|
||||
'url' => 'https://woocommerce.com/products/example-channel',
|
||||
'direct_install' => true,
|
||||
'icon' => 'https://woocommerce.com/example.svg',
|
||||
'product' => 'example-channel',
|
||||
'plugin' => 'example-channel/example-channel.php',
|
||||
'categories' => [ MarketingSpecs::MARKETING_EXTENSION_CATEGORY_SLUG ],
|
||||
'subcategories' => [
|
||||
[
|
||||
'slug' => MarketingSpecs::MARKETING_CHANNEL_SUBCATEGORY_SLUG,
|
||||
'name' => 'Sales channels',
|
||||
],
|
||||
],
|
||||
'tags' => [],
|
||||
],
|
||||
[
|
||||
'title' => 'Example Marketing Extension',
|
||||
'description' => 'Automate your customer communications, etc.',
|
||||
'url' => 'https://woocommerce.com/products/example-marketing-extension',
|
||||
'direct_install' => true,
|
||||
'icon' => 'https://woocommerce.com/example-marketing-extension.svg',
|
||||
'product' => 'example-marketing-extension',
|
||||
'plugin' => 'example-marketing-extension/example-marketing-extension.php',
|
||||
'categories' => [ MarketingSpecs::MARKETING_EXTENSION_CATEGORY_SLUG ],
|
||||
'subcategories' => [
|
||||
[
|
||||
'slug' => 'email',
|
||||
'name' => 'Email',
|
||||
],
|
||||
],
|
||||
'tags' => [],
|
||||
],
|
||||
[
|
||||
'title' => 'Example NON Marketing Extension',
|
||||
'description' => 'Handle coupons, etc.',
|
||||
'url' => 'https://woocommerce.com/products/example-random-extension',
|
||||
'direct_install' => true,
|
||||
'icon' => 'https://woocommerce.com/example-random-extension.svg',
|
||||
'product' => 'example-random-extension',
|
||||
'plugin' => 'example-random-extension/example-random-extension.php',
|
||||
'categories' => [ 'coupons' ],
|
||||
'subcategories' => [],
|
||||
'tags' => [],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the marketing channel recommendations are returned by the endpoint.
|
||||
*/
|
||||
public function test_returns_recommended_marketing_channels() {
|
||||
$endpoint = self::ENDPOINT;
|
||||
$request = new WP_REST_Request( 'GET', $endpoint );
|
||||
$request->set_query_params( [ 'category' => 'channels' ] );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertCount( 1, $data );
|
||||
$this->assertEquals( 'Example Marketing Channel', $data[0]['title'] );
|
||||
$this->assertEquals( 'example-channel', $data[0]['product'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the marketing extension recommendations are returned by the endpoint.
|
||||
*/
|
||||
public function test_returns_recommended_marketing_extensions() {
|
||||
$endpoint = self::ENDPOINT;
|
||||
$request = new WP_REST_Request( 'GET', $endpoint );
|
||||
$request->set_query_params( [ 'category' => 'extensions' ] );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertCount( 1, $data );
|
||||
$this->assertEquals( 'Example Marketing Extension', $data[0]['title'] );
|
||||
$this->assertEquals( 'example-marketing-extension', $data[0]['product'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the endpoint returns an error if the provided category is invalid.
|
||||
*/
|
||||
public function test_returns_error_if_invalid_category_provided() {
|
||||
$endpoint = self::ENDPOINT;
|
||||
$request = new WP_REST_Request( 'GET', $endpoint );
|
||||
$request->set_query_params( [ 'category' => 'test-non-existing-invalid-category' ] );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
$this->assertEquals( 'rest_invalid_param', $data['code'] );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\Marketing;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingCampaign;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingCampaignType;
|
||||
use Automattic\WooCommerce\Admin\Marketing\Price;
|
||||
use WC_Unit_Test_Case;
|
||||
|
||||
/**
|
||||
* Tests for the MarketingCampaign class.
|
||||
*/
|
||||
class MarketingCampaignTest extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* @testdox `get_id`, `get_type`, `get_title`, `get_manage_url`, and `get_cost` return the class properties set by the constructor.
|
||||
*/
|
||||
public function test_get_methods_return_properties() {
|
||||
$test_campaign_type_1 = $this->createMock( MarketingCampaignType::class );
|
||||
|
||||
$marketing_campaign = new MarketingCampaign( '1234', $test_campaign_type_1, 'Ad #1234', 'https://example.com/manage-campaigns', new Price( '1000', 'USD' ) );
|
||||
|
||||
$this->assertEquals( '1234', $marketing_campaign->get_id() );
|
||||
$this->assertEquals( $test_campaign_type_1, $marketing_campaign->get_type() );
|
||||
$this->assertEquals( 'Ad #1234', $marketing_campaign->get_title() );
|
||||
$this->assertEquals( 'https://example.com/manage-campaigns', $marketing_campaign->get_manage_url() );
|
||||
$this->assertNotNull( $marketing_campaign->get_cost() );
|
||||
$this->assertEquals( 'USD', $marketing_campaign->get_cost()->get_currency() );
|
||||
$this->assertEquals( '1000', $marketing_campaign->get_cost()->get_value() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `cost` property can be null.
|
||||
*/
|
||||
public function test_cost_can_be_null() {
|
||||
$test_campaign_type_1 = $this->createMock( MarketingCampaignType::class );
|
||||
|
||||
$marketing_campaign = new MarketingCampaign( '1234', $test_campaign_type_1, 'Ad #1234', 'https://example.com/manage-campaigns' );
|
||||
|
||||
$this->assertNull( $marketing_campaign->get_cost() );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\Marketing;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannelInterface;
|
||||
use Automattic\WooCommerce\Admin\Marketing\MarketingChannels;
|
||||
use WC_Unit_Test_Case;
|
||||
|
||||
/**
|
||||
* Tests for the MarketingChannels class.
|
||||
*/
|
||||
class MarketingChannelsTest extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Runs before each test.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
remove_all_filters( 'woocommerce_marketing_channels' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox A marketing channel can be registered using the `register` method if the same channel slug is NOT previously registered.
|
||||
*/
|
||||
public function test_registers_channel() {
|
||||
$test_channel = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
|
||||
$marketing_channels = new MarketingChannels();
|
||||
$marketing_channels->register( $test_channel );
|
||||
|
||||
$this->assertNotEmpty( $marketing_channels->get_registered_channels() );
|
||||
$this->assertEquals( $test_channel, $marketing_channels->get_registered_channels()[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox A marketing channel can NOT be registered using the `register` method if it is previously registered.
|
||||
*/
|
||||
public function test_throws_exception_if_registering_existing_channels() {
|
||||
$test_channel_1 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
|
||||
$test_channel_1_duplicate = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1_duplicate->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
|
||||
$marketing_channels = new MarketingChannels();
|
||||
$marketing_channels->register( $test_channel_1 );
|
||||
|
||||
$this->expectException( \Exception::class );
|
||||
$marketing_channels->register( $test_channel_1_duplicate );
|
||||
|
||||
$this->assertCount( 1, $marketing_channels->get_registered_channels() );
|
||||
$this->assertEquals( $test_channel_1, $marketing_channels->get_registered_channels()[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox A marketing channel can be registered using the `woocommerce_marketing_channels` WordPress filter if the same channel slug is NOT previously registered.
|
||||
*/
|
||||
public function test_registers_channel_using_wp_filter() {
|
||||
$test_channel = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
|
||||
$marketing_channels = new MarketingChannels();
|
||||
|
||||
add_filter(
|
||||
'woocommerce_marketing_channels',
|
||||
function ( array $channels ) use ( $test_channel ) {
|
||||
$channels[ $test_channel->get_slug() ] = $test_channel;
|
||||
|
||||
return $channels;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertNotEmpty( $marketing_channels->get_registered_channels() );
|
||||
$this->assertEquals( $test_channel, $marketing_channels->get_registered_channels()[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox A marketing channel can NOT be registered using the `woocommerce_marketing_channels` WordPress filter if it is previously registered.
|
||||
*/
|
||||
public function test_overrides_existing_channel_if_registered_using_wp_filter() {
|
||||
$marketing_channels = new MarketingChannels();
|
||||
|
||||
$test_channel_1 = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
|
||||
$marketing_channels->register( $test_channel_1 );
|
||||
|
||||
$test_channel_1_duplicate = $this->createMock( MarketingChannelInterface::class );
|
||||
$test_channel_1_duplicate->expects( $this->any() )->method( 'get_slug' )->willReturn( 'test-channel-1' );
|
||||
|
||||
add_filter(
|
||||
'woocommerce_marketing_channels',
|
||||
function ( array $channels ) use ( $test_channel_1_duplicate ) {
|
||||
$channels[ $test_channel_1_duplicate->get_slug() ] = $test_channel_1_duplicate;
|
||||
|
||||
return $channels;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertCount( 1, $marketing_channels->get_registered_channels() );
|
||||
$this->assertEquals( $test_channel_1_duplicate, $marketing_channels->get_registered_channels()[0] );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue