Multichannel Marketing - API (#36222)

* Rename `get_errors_no` to `get_errors_count`

* Remove the validation for marketing channel slugs

Do not check if the marketing channel's slug exists in the list returned by WooCommerce.com Recommendation API. This essentially allows any third-party extension to register as a marketing channel.

* Revert InstalledExtensions

The InstalledExtensions class will be used by the previous generation of Marketing dashboard (if the user has not enabled the new "Marketing" feature); therefore, it's best to restore it to the original code.

* Fix code style

* Add channel property to MarketingCampaign

* Add methods to filter the recommended marketing channels and extensions

* Add `marketing/recommendations` API

* Add unit tests for `marketing/recommendations` API

* Add `marketing/channels` API

* Add unit tests for `marketing/channels` API

* Add `marketing/campaigns` API

* Add unit tests for `marketing/campaigns` API

* Translate Exception message

* Remove doc references to predetermined list of marketing channels

* Add `unregister_all` method

To allow unregistering all marketing channels.

* Unregister all channels on test tear down

* Change API access denied authorization code

* Change API access permission

* Add MarketingCampaignType class

This allows defining campaign types for each marketing channel.

* Add campaign type property to campaign class

* Add `marketing/campaign-types` API

This API returns the aggregated list of supported marketing campaign types for all registered marketing channels.

* Add unit tests for `marketing/campaign-types` API

* Remove unused jsonSerialize method

* Fix unit tests

Co-authored-by: Nima <nima.karimi@automattic.com>
This commit is contained in:
Nima Karimi 2023-01-13 16:54:48 +00:00 committed by GitHub
parent 3fb90016dc
commit b2ff0ba1a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1602 additions and 62 deletions

View File

@ -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',

View File

@ -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 );
}
}

View File

@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -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 );
}
}

View File

@ -7,14 +7,12 @@
namespace Automattic\WooCommerce\Admin\Marketing;
use JsonSerializable;
/**
* MarketingCampaign class
*
* @since x.x.x
*/
class MarketingCampaign implements JsonSerializable {
class MarketingCampaign {
/**
* The unique identifier.
*
@ -22,6 +20,13 @@ class MarketingCampaign implements JsonSerializable {
*/
protected $id;
/**
* The marketing campaign type.
*
* @var MarketingCampaignType
*/
protected $type;
/**
* Title of the marketing campaign.
*
@ -47,12 +52,14 @@ class MarketingCampaign implements JsonSerializable {
* 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, string $title, string $manage_url, Price $cost = null ) {
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;
@ -67,6 +74,15 @@ class MarketingCampaign implements JsonSerializable {
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.
*
@ -93,18 +109,4 @@ class MarketingCampaign implements JsonSerializable {
public function get_cost(): ?Price {
return $this->cost;
}
/**
* Serialize the marketing campaign data.
*
* @return array
*/
public function jsonSerialize() {
return [
'id' => $this->get_id(),
'title' => $this->get_title(),
'manage_url' => $this->get_manage_url(),
'cost' => $this->get_cost(),
];
}
}

View File

@ -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;
}
}

View File

@ -73,6 +73,13 @@ interface MarketingChannelInterface {
*/
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.
*

View File

@ -37,6 +37,15 @@ class MarketingChannels {
$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.
*

View File

@ -5,14 +5,12 @@
namespace Automattic\WooCommerce\Admin\Marketing;
use JsonSerializable;
/**
* Price class
*
* @since x.x.x
*/
class Price implements JsonSerializable {
class Price {
/**
* The price.
*
@ -55,16 +53,4 @@ class Price implements JsonSerializable {
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(),
];
}
}

View File

@ -28,6 +28,20 @@ class MarketingSpecs {
*/
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
*
@ -61,6 +75,64 @@ class MarketingSpecs {
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
*

View File

@ -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' )
);
}
}

View File

@ -0,0 +1,155 @@
<?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\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 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 type.
$test_campaign_type_1 = $this->createMock( MarketingCampaignType::class );
$test_campaign_type_1->expects( $this->any() )->method( 'get_channel' )->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_type' )->willReturn( $test_campaign_type_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 type for the second marketing channel.
$test_campaign_type_2 = $this->createMock( MarketingCampaignType::class );
$test_campaign_type_2->expects( $this->any() )->method( 'get_channel' )->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_type' )->willReturn( $test_campaign_type_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'] );
}
}

View File

@ -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'] );
}
}

View File

@ -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'] );
}
}

View File

@ -3,6 +3,7 @@
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;
@ -12,12 +13,15 @@ use WC_Unit_Test_Case;
class MarketingCampaignTest extends WC_Unit_Test_Case {
/**
* @testdox `get_id`, `get_title`, `get_manage_url`, and `get_cost` return the class properties set by the constructor.
* @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() {
$marketing_campaign = new MarketingCampaign( '1234', 'Ad #1234', 'https://example.com/manage-campaigns', new Price( '1000', 'USD' ) );
$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() );
@ -29,30 +33,10 @@ class MarketingCampaignTest extends WC_Unit_Test_Case {
* @testdox `cost` property can be null.
*/
public function test_cost_can_be_null() {
$marketing_campaign = new MarketingCampaign( '1234', 'Ad #1234', 'https://example.com/manage-campaigns' );
$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() );
}
/**
* @testdox It can be serialized to JSON including all its properties.
*/
public function test_can_be_serialized_to_json() {
$marketing_campaign = new MarketingCampaign( '1234', 'Ad #1234', 'https://example.com/manage-campaigns', new Price( '1000', 'USD' ) );
$json = wp_json_encode( $marketing_campaign );
$this->assertNotEmpty( $json );
$this->assertEqualSets(
[
'id' => $marketing_campaign->get_id(),
'title' => $marketing_campaign->get_title(),
'manage_url' => $marketing_campaign->get_manage_url(),
'cost' => [
'value' => $marketing_campaign->get_cost()->get_value(),
'currency' => $marketing_campaign->get_cost()->get_currency(),
],
],
json_decode( $json, true )
);
}
}