2026-08-23 15:38:14 +02:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* REST API for remote management.
|
|
|
|
|
*
|
|
|
|
|
* All routes require an authenticated user with the manage_woocommerce
|
|
|
|
|
* capability (e.g. via a WordPress Application Password over HTTPS).
|
|
|
|
|
* There is deliberately no token-in-code or unauthenticated access.
|
|
|
|
|
*
|
|
|
|
|
* @package WC_Email_Discount_Manager
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Registers wc-edm/v1 REST routes for groups, emails and shipping rules.
|
|
|
|
|
*/
|
|
|
|
|
class WC_EDM_REST_API {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* REST namespace.
|
|
|
|
|
*/
|
|
|
|
|
const REST_NAMESPACE = 'wc-edm/v1';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hook route registration.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct() {
|
|
|
|
|
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
|
|
|
|
add_filter( 'rest_request_after_callbacks', array( $this, 'log_request' ), 10, 3 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Permission check shared by all routes.
|
|
|
|
|
*
|
|
|
|
|
* Rate limiting runs before the capability check so that unauthenticated
|
|
|
|
|
* floods are throttled too.
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request|null $request Current request.
|
|
|
|
|
* @return bool|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function permission_check( $request = null ) {
|
|
|
|
|
$method = $request instanceof WP_REST_Request ? $request->get_method() : 'GET';
|
|
|
|
|
$rate = WC_EDM_Rate_Limiter::check( $method );
|
|
|
|
|
|
|
|
|
|
if ( $rate['limited'] ) {
|
|
|
|
|
return new WP_Error(
|
|
|
|
|
'wc_edm_rate_limited',
|
|
|
|
|
__( 'Too many requests. Please slow down.', 'wc-email-discount' ),
|
|
|
|
|
array(
|
|
|
|
|
'status' => 429,
|
|
|
|
|
'retry_after' => $rate['retry_after'],
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new WP_Error(
|
|
|
|
|
'wc_edm_forbidden',
|
|
|
|
|
__( 'You are not allowed to manage email discounts.', 'wc-email-discount' ),
|
|
|
|
|
array( 'status' => rest_authorization_required_code() )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Write an audit entry for wc-edm requests.
|
|
|
|
|
*
|
|
|
|
|
* Runs after the route callback (and also when a permission check failed),
|
|
|
|
|
* so both successful writes and rejected attempts are recorded. Successful
|
|
|
|
|
* reads are skipped to keep the log signal-heavy.
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Response|WP_HTTP_Response|WP_Error $response Result to send.
|
|
|
|
|
* @param array $handler Route handler.
|
|
|
|
|
* @param WP_REST_Request $request Current request.
|
|
|
|
|
* @return mixed The unmodified response.
|
|
|
|
|
*/
|
|
|
|
|
public function log_request( $response, $handler, $request ) {
|
|
|
|
|
if ( ! $request instanceof WP_REST_Request ) {
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$route = $request->get_route();
|
|
|
|
|
|
|
|
|
|
if ( 0 !== strpos( ltrim( $route, '/' ), self::REST_NAMESPACE ) ) {
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$method = $request->get_method();
|
|
|
|
|
$is_write = WC_EDM_Rate_Limiter::is_write_method( $method );
|
|
|
|
|
$details = array();
|
|
|
|
|
$outcome = 'allowed';
|
|
|
|
|
$status = 200;
|
|
|
|
|
|
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
|
|
|
$error_data = $response->get_error_data();
|
|
|
|
|
$status = is_array( $error_data ) && isset( $error_data['status'] ) ? (int) $error_data['status'] : 500;
|
|
|
|
|
$outcome = 'wc_edm_rate_limited' === $response->get_error_code() ? 'rate_limited' : 'denied';
|
|
|
|
|
|
|
|
|
|
$details['code'] = $response->get_error_code();
|
|
|
|
|
} elseif ( $response instanceof WP_HTTP_Response ) {
|
|
|
|
|
$status = $response->get_status();
|
|
|
|
|
|
|
|
|
|
if ( $status >= 400 ) {
|
|
|
|
|
$outcome = 'denied';
|
|
|
|
|
$data = $response->get_data();
|
|
|
|
|
$details['code'] = is_array( $data ) && isset( $data['code'] ) ? $data['code'] : 'error';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Successful reads are noise; everything else is worth keeping.
|
|
|
|
|
if ( 'allowed' === $outcome && ! $is_write ) {
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't let a blocked caller flood the log with identical rows.
|
|
|
|
|
if ( 'rate_limited' === $outcome && ! WC_EDM_Rate_Limiter::should_log_limit_hit() ) {
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WC_EDM_Audit_Log::record( $outcome, $method, $route, $status, $details );
|
|
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register all routes.
|
|
|
|
|
*/
|
|
|
|
|
public function register_routes() {
|
|
|
|
|
$perm = array( $this, 'permission_check' );
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/status',
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_status' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/groups',
|
|
|
|
|
array(
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_groups' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
|
|
|
'callback' => array( $this, 'create_group' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/groups/(?P<id>\d+)',
|
|
|
|
|
array(
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_group' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::EDITABLE,
|
|
|
|
|
'callback' => array( $this, 'update_group' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::DELETABLE,
|
|
|
|
|
'callback' => array( $this, 'delete_group' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/emails',
|
|
|
|
|
array(
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_emails' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
|
|
|
'callback' => array( $this, 'create_email' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/emails/(?P<id>\d+)',
|
|
|
|
|
array(
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_email' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::EDITABLE,
|
|
|
|
|
'callback' => array( $this, 'update_email' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::DELETABLE,
|
|
|
|
|
'callback' => array( $this, 'delete_email' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/shipping-methods',
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_shipping_methods' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/groups/(?P<id>\d+)/shipping-rules',
|
|
|
|
|
array(
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_shipping_rules' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::EDITABLE,
|
|
|
|
|
'callback' => array( $this, 'update_shipping_rules' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/audit-log',
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'get_audit_log' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
'args' => array(
|
|
|
|
|
'limit' => array(
|
|
|
|
|
'type' => 'integer',
|
|
|
|
|
'default' => 50,
|
|
|
|
|
),
|
|
|
|
|
'offset' => array(
|
|
|
|
|
'type' => 'integer',
|
|
|
|
|
'default' => 0,
|
|
|
|
|
),
|
|
|
|
|
'outcome' => array(
|
|
|
|
|
'type' => 'string',
|
|
|
|
|
'enum' => array( 'allowed', 'denied', 'rate_limited' ),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
register_rest_route(
|
|
|
|
|
self::REST_NAMESPACE,
|
|
|
|
|
'/match',
|
|
|
|
|
array(
|
|
|
|
|
'methods' => WP_REST_Server::READABLE,
|
|
|
|
|
'callback' => array( $this, 'match_email' ),
|
|
|
|
|
'permission_callback' => $perm,
|
|
|
|
|
'args' => array(
|
|
|
|
|
'email' => array(
|
|
|
|
|
'required' => true,
|
|
|
|
|
'type' => 'string',
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Formatters
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format a group row for API output.
|
|
|
|
|
*
|
|
|
|
|
* @param object $group Group row.
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
private function format_group( $group ) {
|
|
|
|
|
return array(
|
|
|
|
|
'id' => (int) $group->id,
|
|
|
|
|
'name' => $group->name,
|
|
|
|
|
'discount_percent' => (float) $group->discount_percent,
|
|
|
|
|
'category_ids' => WC_EDM_DB::get_group_category_ids( (int) $group->id ),
|
|
|
|
|
'created_at' => $group->created_at,
|
|
|
|
|
'updated_at' => $group->updated_at,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format an email assignment row for API output.
|
|
|
|
|
*
|
|
|
|
|
* @param object $assignment Email row.
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
private function format_email( $assignment ) {
|
|
|
|
|
return array(
|
|
|
|
|
'id' => (int) $assignment->id,
|
|
|
|
|
'email_pattern' => $assignment->email_pattern,
|
|
|
|
|
'assignment_type' => $assignment->assignment_type,
|
|
|
|
|
'group_id' => $assignment->group_id ? (int) $assignment->group_id : null,
|
|
|
|
|
'coupon_code' => $assignment->coupon_code,
|
|
|
|
|
'custom_discount' => null !== $assignment->custom_discount ? (float) $assignment->custom_discount : null,
|
|
|
|
|
'is_active' => (bool) $assignment->is_active,
|
|
|
|
|
'free_shipping' => ! empty( $assignment->free_shipping ),
|
|
|
|
|
'created_at' => $assignment->created_at,
|
|
|
|
|
'updated_at' => $assignment->updated_at,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Standard 404 error.
|
|
|
|
|
*
|
|
|
|
|
* @return WP_Error
|
|
|
|
|
*/
|
|
|
|
|
private function not_found() {
|
|
|
|
|
return new WP_Error( 'wc_edm_not_found', __( 'Resource not found.', 'wc-email-discount' ), array( 'status' => 404 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Status
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /status — connectivity test plus quick stats.
|
|
|
|
|
*
|
|
|
|
|
* @return WP_REST_Response
|
|
|
|
|
*/
|
|
|
|
|
public function get_status() {
|
|
|
|
|
return rest_ensure_response(
|
|
|
|
|
array(
|
|
|
|
|
'plugin' => 'woocommerce-email-discount-manager',
|
|
|
|
|
'version' => WC_EDM_VERSION,
|
|
|
|
|
'woocommerce' => defined( 'WC_VERSION' ) ? WC_VERSION : null,
|
|
|
|
|
'groups' => WC_EDM_DB::count_groups(),
|
|
|
|
|
'email_assignments' => WC_EDM_DB::count_emails(),
|
|
|
|
|
'active_assignments' => WC_EDM_DB::count_emails( true ),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Groups
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /groups
|
|
|
|
|
*
|
|
|
|
|
* @return WP_REST_Response
|
|
|
|
|
*/
|
|
|
|
|
public function get_groups() {
|
|
|
|
|
return rest_ensure_response( array_map( array( $this, 'format_group' ), WC_EDM_DB::get_groups() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /groups/{id}
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function get_group( $request ) {
|
|
|
|
|
$group = WC_EDM_DB::get_group( (int) $request['id'] );
|
|
|
|
|
return $group ? rest_ensure_response( $this->format_group( $group ) ) : $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /groups
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function create_group( $request ) {
|
|
|
|
|
$name = sanitize_text_field( (string) $request['name'] );
|
|
|
|
|
|
|
|
|
|
if ( '' === $name ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_invalid', __( 'A group name is required.', 'wc-email-discount' ), array( 'status' => 400 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$id = WC_EDM_DB::insert_group(
|
|
|
|
|
array(
|
|
|
|
|
'name' => $name,
|
|
|
|
|
'discount_percent' => WC_EDM_Helpers::sanitize_percent( $request['discount_percent'] ?? 0 ),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( ! $id ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_db_error', __( 'Could not create the group.', 'wc-email-discount' ), array( 'status' => 500 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( is_array( $request['category_ids'] ) ) {
|
|
|
|
|
WC_EDM_DB::set_group_categories( $id, array_map( 'absint', $request['category_ids'] ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response( $this->format_group( WC_EDM_DB::get_group( $id ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PUT/PATCH /groups/{id}
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function update_group( $request ) {
|
|
|
|
|
$id = (int) $request['id'];
|
|
|
|
|
$group = WC_EDM_DB::get_group( $id );
|
|
|
|
|
|
|
|
|
|
if ( ! $group ) {
|
|
|
|
|
return $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$name = null !== $request['name'] ? sanitize_text_field( (string) $request['name'] ) : $group->name;
|
|
|
|
|
$percent = null !== $request['discount_percent'] ? WC_EDM_Helpers::sanitize_percent( $request['discount_percent'] ) : (float) $group->discount_percent;
|
|
|
|
|
|
|
|
|
|
if ( '' === $name ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_invalid', __( 'A group name is required.', 'wc-email-discount' ), array( 'status' => 400 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WC_EDM_DB::update_group(
|
|
|
|
|
$id,
|
|
|
|
|
array(
|
|
|
|
|
'name' => $name,
|
|
|
|
|
'discount_percent' => $percent,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( is_array( $request['category_ids'] ) ) {
|
|
|
|
|
WC_EDM_DB::set_group_categories( $id, array_map( 'absint', $request['category_ids'] ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response( $this->format_group( WC_EDM_DB::get_group( $id ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DELETE /groups/{id}
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function delete_group( $request ) {
|
|
|
|
|
$id = (int) $request['id'];
|
|
|
|
|
|
|
|
|
|
if ( ! WC_EDM_DB::get_group( $id ) ) {
|
|
|
|
|
return $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WC_EDM_DB::delete_group( $id );
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response( array( 'deleted' => true, 'id' => $id ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Email assignments
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /emails
|
|
|
|
|
*
|
|
|
|
|
* @return WP_REST_Response
|
|
|
|
|
*/
|
|
|
|
|
public function get_emails() {
|
|
|
|
|
return rest_ensure_response( array_map( array( $this, 'format_email' ), WC_EDM_DB::get_emails() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /emails/{id}
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function get_email( $request ) {
|
|
|
|
|
$assignment = WC_EDM_DB::get_email( (int) $request['id'] );
|
|
|
|
|
return $assignment ? rest_ensure_response( $this->format_email( $assignment ) ) : $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate and normalize email assignment input.
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @param object|null $existing Existing row when updating.
|
|
|
|
|
* @return array|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
private function prepare_email_data( $request, $existing = null ) {
|
|
|
|
|
$pattern = null !== $request['email_pattern']
|
|
|
|
|
? strtolower( sanitize_text_field( (string) $request['email_pattern'] ) )
|
|
|
|
|
: ( $existing ? $existing->email_pattern : '' );
|
|
|
|
|
|
|
|
|
|
$type = null !== $request['assignment_type']
|
|
|
|
|
? sanitize_key( (string) $request['assignment_type'] )
|
|
|
|
|
: ( $existing ? $existing->assignment_type : 'group' );
|
|
|
|
|
|
|
|
|
|
if ( '' === $pattern ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_invalid', __( 'An email pattern is required.', 'wc-email-discount' ), array( 'status' => 400 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ! in_array( $type, array( 'group', 'coupon', 'custom' ), true ) ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_invalid', __( 'assignment_type must be one of: group, coupon, custom.', 'wc-email-discount' ), array( 'status' => 400 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = array(
|
|
|
|
|
'email_pattern' => $pattern,
|
|
|
|
|
'assignment_type' => $type,
|
|
|
|
|
'group_id' => null,
|
|
|
|
|
'coupon_code' => null,
|
|
|
|
|
'custom_discount' => null,
|
|
|
|
|
'is_active' => null !== $request['is_active']
|
|
|
|
|
? ( rest_sanitize_boolean( $request['is_active'] ) ? 1 : 0 )
|
|
|
|
|
: ( $existing ? (int) $existing->is_active : 1 ),
|
|
|
|
|
'free_shipping' => null !== $request['free_shipping']
|
|
|
|
|
? ( rest_sanitize_boolean( $request['free_shipping'] ) ? 1 : 0 )
|
|
|
|
|
: ( $existing ? (int) ! empty( $existing->free_shipping ) : 0 ),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
switch ( $type ) {
|
|
|
|
|
case 'group':
|
|
|
|
|
$group_id = null !== $request['group_id']
|
|
|
|
|
? absint( $request['group_id'] )
|
|
|
|
|
: ( $existing && $existing->group_id ? (int) $existing->group_id : 0 );
|
|
|
|
|
|
|
|
|
|
if ( ! $group_id || ! WC_EDM_DB::get_group( $group_id ) ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_invalid', __( 'A valid group_id is required for group assignments.', 'wc-email-discount' ), array( 'status' => 400 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data['group_id'] = $group_id;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'coupon':
|
|
|
|
|
$coupon = null !== $request['coupon_code']
|
|
|
|
|
? wc_format_coupon_code( (string) $request['coupon_code'] )
|
|
|
|
|
: ( $existing ? (string) $existing->coupon_code : '' );
|
|
|
|
|
|
|
|
|
|
if ( '' === $coupon ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_invalid', __( 'A coupon_code is required for coupon assignments.', 'wc-email-discount' ), array( 'status' => 400 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data['coupon_code'] = $coupon;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'custom':
|
|
|
|
|
$data['custom_discount'] = null !== $request['custom_discount']
|
|
|
|
|
? WC_EDM_Helpers::sanitize_percent( $request['custom_discount'] )
|
|
|
|
|
: ( $existing && null !== $existing->custom_discount ? (float) $existing->custom_discount : 0 );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /emails
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function create_email( $request ) {
|
|
|
|
|
$data = $this->prepare_email_data( $request );
|
|
|
|
|
|
|
|
|
|
if ( is_wp_error( $data ) ) {
|
|
|
|
|
return $data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$id = WC_EDM_DB::insert_email( $data );
|
|
|
|
|
|
|
|
|
|
if ( ! $id ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_db_error', __( 'Could not create the assignment.', 'wc-email-discount' ), array( 'status' => 500 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response( $this->format_email( WC_EDM_DB::get_email( $id ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PUT/PATCH /emails/{id}
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function update_email( $request ) {
|
|
|
|
|
$id = (int) $request['id'];
|
|
|
|
|
$existing = WC_EDM_DB::get_email( $id );
|
|
|
|
|
|
|
|
|
|
if ( ! $existing ) {
|
|
|
|
|
return $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $this->prepare_email_data( $request, $existing );
|
|
|
|
|
|
|
|
|
|
if ( is_wp_error( $data ) ) {
|
|
|
|
|
return $data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WC_EDM_DB::update_email( $id, $data );
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response( $this->format_email( WC_EDM_DB::get_email( $id ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DELETE /emails/{id}
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function delete_email( $request ) {
|
|
|
|
|
$id = (int) $request['id'];
|
|
|
|
|
|
|
|
|
|
if ( ! WC_EDM_DB::get_email( $id ) ) {
|
|
|
|
|
return $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WC_EDM_DB::delete_email( $id );
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response( array( 'deleted' => true, 'id' => $id ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Shipping
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /shipping-methods — all shipping method instances across zones.
|
|
|
|
|
*
|
|
|
|
|
* @return WP_REST_Response
|
|
|
|
|
*/
|
|
|
|
|
public function get_shipping_methods() {
|
|
|
|
|
return rest_ensure_response( array_values( WC_EDM_Helpers::get_all_shipping_methods() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /groups/{id}/shipping-rules
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function get_shipping_rules( $request ) {
|
|
|
|
|
$group_id = (int) $request['id'];
|
|
|
|
|
|
|
|
|
|
if ( ! WC_EDM_DB::get_group( $group_id ) ) {
|
|
|
|
|
return $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rules = array();
|
|
|
|
|
|
|
|
|
|
foreach ( WC_EDM_DB::get_shipping_rules( $group_id ) as $rule ) {
|
|
|
|
|
$rules[] = array(
|
|
|
|
|
'shipping_method_id' => $rule->shipping_method_id,
|
|
|
|
|
'action' => $rule->action,
|
|
|
|
|
'sort_order' => (int) $rule->sort_order,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response( $rules );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PUT /groups/{id}/shipping-rules — replace all rules for the group.
|
|
|
|
|
*
|
|
|
|
|
* Body: { "rules": [ { "shipping_method_id": "flat_rate:3", "action": "show" }, ... ] }
|
|
|
|
|
* Sort order follows array order. An empty rules array clears all rules.
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response|WP_Error
|
|
|
|
|
*/
|
|
|
|
|
public function update_shipping_rules( $request ) {
|
|
|
|
|
$group_id = (int) $request['id'];
|
|
|
|
|
|
|
|
|
|
if ( ! WC_EDM_DB::get_group( $group_id ) ) {
|
|
|
|
|
return $this->not_found();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$raw = $request['rules'];
|
|
|
|
|
|
|
|
|
|
if ( ! is_array( $raw ) ) {
|
|
|
|
|
return new WP_Error( 'wc_edm_invalid', __( 'A rules array is required.', 'wc-email-discount' ), array( 'status' => 400 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rules = array();
|
|
|
|
|
|
|
|
|
|
foreach ( array_values( $raw ) as $index => $rule ) {
|
|
|
|
|
$method_id = isset( $rule['shipping_method_id'] ) ? sanitize_text_field( (string) $rule['shipping_method_id'] ) : '';
|
|
|
|
|
$action = isset( $rule['action'] ) ? sanitize_key( (string) $rule['action'] ) : '';
|
|
|
|
|
|
2026-10-02 12:28:45 +02:00
|
|
|
if ( '' === $method_id || ! in_array( $action, array( 'show', 'hide', 'only' ), true ) ) {
|
2026-08-23 15:38:14 +02:00
|
|
|
return new WP_Error(
|
|
|
|
|
'wc_edm_invalid',
|
2026-10-02 12:28:45 +02:00
|
|
|
__( 'Each rule needs a shipping_method_id and an action of "show", "hide" or "only".', 'wc-email-discount' ),
|
2026-08-23 15:38:14 +02:00
|
|
|
array( 'status' => 400 )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rules[] = array(
|
|
|
|
|
'shipping_method_id' => $method_id,
|
|
|
|
|
'action' => $action,
|
|
|
|
|
'sort_order' => $index,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WC_EDM_DB::replace_shipping_rules( $group_id, $rules );
|
|
|
|
|
|
|
|
|
|
return $this->get_shipping_rules( $request );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Audit log
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /audit-log — recent REST activity, newest first.
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response
|
|
|
|
|
*/
|
|
|
|
|
public function get_audit_log( $request ) {
|
|
|
|
|
$limit = min( 200, max( 1, (int) $request['limit'] ) );
|
|
|
|
|
$offset = max( 0, (int) $request['offset'] );
|
|
|
|
|
$outcome = $request['outcome'] ? sanitize_key( (string) $request['outcome'] ) : '';
|
|
|
|
|
|
|
|
|
|
$entries = array();
|
|
|
|
|
|
|
|
|
|
foreach ( WC_EDM_DB::get_audit_entries( $limit, $offset, $outcome ) as $entry ) {
|
|
|
|
|
$entries[] = array(
|
|
|
|
|
'id' => (int) $entry->id,
|
|
|
|
|
'created_at' => $entry->created_at,
|
|
|
|
|
'user_id' => (int) $entry->user_id,
|
|
|
|
|
'user_login' => $entry->user_login,
|
|
|
|
|
'ip' => $entry->ip,
|
|
|
|
|
'method' => $entry->method,
|
|
|
|
|
'route' => $entry->route,
|
|
|
|
|
'status' => (int) $entry->status,
|
|
|
|
|
'outcome' => $entry->outcome,
|
|
|
|
|
'details' => $entry->details ? json_decode( $entry->details, true ) : null,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$response = rest_ensure_response( $entries );
|
|
|
|
|
$response->header( 'X-WP-Total', (string) WC_EDM_DB::count_audit_entries( $outcome ) );
|
|
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Match test
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /match?email=... — test which assignment an email would get.
|
|
|
|
|
*
|
|
|
|
|
* @param WP_REST_Request $request Request.
|
|
|
|
|
* @return WP_REST_Response
|
|
|
|
|
*/
|
|
|
|
|
public function match_email( $request ) {
|
|
|
|
|
$email = sanitize_email( (string) $request['email'] );
|
|
|
|
|
$match = $email ? WC_EDM_Helpers::find_email_match( $email ) : null;
|
|
|
|
|
|
|
|
|
|
return rest_ensure_response(
|
|
|
|
|
array(
|
|
|
|
|
'email' => $email,
|
|
|
|
|
'matched' => (bool) $match,
|
|
|
|
|
'match' => $match ? $this->format_email( $match ) : null,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|