* 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;
defined( 'ABSPATH' ) || exit;
* MarketingCampaigns Controller.
* @extends WC_REST_Controller
class MarketingCampaigns extends WC_REST_Controller {
protected $namespace = 'wc-admin';
protected $rest_base = 'marketing/campaigns';
public function register_routes() {
'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() ) );
* 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.
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 );
$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 ( $prev_page > $max_pages ) {
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
if ( $max_pages > $page ) {
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
* Get formatted price based on Price type.
* This uses plugins/woocommerce/i18n/currency-info.php and plugins/woocommerce/i18n/locale-info.php to get option object based on $price->currency.
* - When $price->currency is 'USD' and $price->value is '1000', it should return '$1000.00'.
* - When $price->currency is 'JPY' and $price->value is '1000', it should return 'Â¥1,000'.
* - When $price->currency is 'AED' and $price->value is '1000', it should return '5.000,00 د.إ'.
* @param Price $price Price object.
* @return String formatted price.
private function get_formatted_price( $price ) {
// Get $num_decimals to be passed to wc_price.
$locale_info_all = include WC()->plugin_path() . '/i18n/locale-info.php';
$locale_index = array_search( $price->get_currency(), array_column( $locale_info_all, 'currency_code' ), true );
$locale = array_values( $locale_info_all )[ $locale_index ];
$num_decimals = $locale['num_decimals'];
// Get $currency_info based on user locale or default locale.
$currency_locales = $locale['locales'];
$user_locale = get_user_locale();
$currency_info = $currency_locales[ $user_locale ] ?? $currency_locales['default'];
// Get $price_format to be passed to wc_price.
$currency_pos = $currency_info['currency_pos'];
$currency_formats = array(
'left_space' => '%1$s %2$s',
'right_space' => '%2$s %1$s',
$price_format = $currency_formats[ $currency_pos ] ?? $currency_formats['left'];
$price_value = wc_format_decimal( $price->get_value() );
$price_formatted = wc_price(
'currency' => $price->get_currency(),
'decimal_separator' => $currency_info['decimal_sep'],
'thousand_separator' => $currency_info['thousand_sep'],
'decimals' => $num_decimals,
'price_format' => $price_format,
return html_entity_decode( wp_strip_all_tags( $price_formatted ) );
* 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 ) {
'channel' => $item->get_type()->get_channel()->get_slug(),
'title' => $item->get_title(),
'manage_url' => $item->get_manage_url(),
if ( $item->get_cost() instanceof Price ) {
'value' => wc_format_decimal( $item->get_cost()->get_value() ),
'currency' => $item->get_cost()->get_currency(),
'formatted' => $this->get_formatted_price( $item->get_cost() ),
if ( $item->get_sales() instanceof Price ) {
'value' => wc_format_decimal( $item->get_sales()->get_value() ),
'currency' => $item->get_sales()->get_currency(),
'formatted' => $this->get_formatted_price( $item->get_sales() ),
$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' => 'http://json-schema.org/draft-04/schema#',
'title' => 'marketing_campaign',
'description' => __( 'The unique identifier for the marketing campaign.', 'woocommerce' ),
'context' => array( 'view' ),
'description' => __( 'The unique identifier for the marketing channel that this campaign belongs to.', 'woocommerce' ),
'context' => array( 'view' ),
'description' => __( 'Title of the marketing campaign.', 'woocommerce' ),
'context' => array( 'view' ),
'description' => __( 'URL to the campaign management page.', 'woocommerce' ),
'context' => array( 'view' ),
'description' => __( 'Cost of the marketing campaign.', 'woocommerce' ),
'context' => array( 'view' ),
'context' => array( 'view' ),
'context' => array( 'view' ),
'description' => __( 'Sales of the marketing campaign.', 'woocommerce' ),
'context' => array( 'view' ),
'context' => array( 'view' ),
'context' => array( 'view' ),
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'] );