Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
wp-content
/
plugins
/
give
/
src
/
API
/
REST
/
V3
/
Routes
/
Subscriptions
:
SubscriptionController.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php namespace Give\API\REST\V3\Routes\Subscriptions; use Exception; use Give\API\REST\V3\Routes\Donors\ValueObjects\DonorAnonymousMode; use Give\API\REST\V3\Routes\Subscriptions\Actions\GetSubscriptionCollectionParams; use Give\API\REST\V3\Routes\Subscriptions\Actions\GetSubscriptionItemSchema; use Give\API\REST\V3\Routes\Subscriptions\Actions\GetSubscriptionSharedParamsForGetMethods; use Give\API\REST\V3\Routes\Subscriptions\DataTransferObjects\SubscriptionCreateData; use Give\API\REST\V3\Routes\Subscriptions\Exceptions\SubscriptionValidationException; use Give\API\REST\V3\Routes\Subscriptions\Fields\SubscriptionFields; use Give\API\REST\V3\Routes\Subscriptions\Permissions\SubscriptionPermissions; use Give\API\REST\V3\Routes\Subscriptions\ValueObjects\SubscriptionRoute; use Give\API\REST\V3\Support\CURIE; use Give\API\REST\V3\Support\Headers; use Give\API\REST\V3\Support\Item; use Give\Subscriptions\Models\Subscription; use Give\Subscriptions\SubscriptionQuery; use Give\Subscriptions\ValueObjects\SubscriptionStatus; use Give\Subscriptions\ViewModels\SubscriptionViewModel; use WP_Error; use WP_REST_Controller; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * The methods using snake case like register_routes() are present in the base class, * and the methods using camel case like deleteItems() are available only on this class. * * @since 4.8.0 */ class SubscriptionController extends WP_REST_Controller { /** * @var string */ protected $namespace; /** * @var string */ protected $rest_base; /** * @since 4.8.0 */ public function __construct() { $this->namespace = SubscriptionRoute::NAMESPACE; $this->rest_base = SubscriptionRoute::BASE; } /** * @since 4.9.0 Move schema key to the route level instead of defining it for each endpoint (which is incorrect) * @since 4.8.0 */ 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' => array_merge($this->get_collection_params(), give(GetSubscriptionSharedParamsForGetMethods::class)()), ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'create_item'], 'permission_callback' => [$this, 'create_item_permissions_check'], 'args' => rest_get_endpoint_args_for_schema($this->get_item_schema(), WP_REST_Server::CREATABLE), ], [ 'methods' => WP_REST_Server::DELETABLE, 'callback' => [$this, 'deleteItems'], 'permission_callback' => [$this, 'delete_items_permissions_check'], 'args' => [ 'ids' => [ 'description' => __('Array of subscription IDs to delete', 'give'), 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'required' => true, ], 'force' => [ 'description' => __('Whether to permanently delete (force=true) or move to trash (force=false, default).', 'give'), 'type' => 'boolean', 'default' => false, ], ], ], 'schema' => [$this, 'get_public_item_schema'], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_item'], 'permission_callback' => [$this, 'get_item_permissions_check'], 'args' => array_merge([ 'id' => [ 'description' => __('The subscription ID.', 'give'), 'type' => 'integer', 'required' => true, ], '_embed' => [ 'description' => __( 'Whether to embed related resources in the response. It can be true when we want to embed all available resources, or a string like "givewp:donor" when we wish to embed only a specific one.', 'give' ), 'type' => [ 'string', 'boolean', ], 'default' => false, ], ], give(GetSubscriptionSharedParamsForGetMethods::class)()), ], [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_item'], 'permission_callback' => [$this, 'update_item_permissions_check'], 'args' => rest_get_endpoint_args_for_schema($this->get_item_schema(), WP_REST_Server::EDITABLE), ], [ 'methods' => WP_REST_Server::DELETABLE, 'callback' => [$this, 'delete_item'], 'permission_callback' => [$this, 'delete_item_permissions_check'], 'args' => [ 'id' => [ 'description' => __('The subscription ID.', 'give'), 'type' => 'integer', 'required' => true, ], 'force' => [ 'description' => __('Whether to permanently delete (force=true) or move to trash (force=false, default).', 'give'), 'type' => 'boolean', 'default' => false, ], ], ], 'schema' => [$this, 'get_public_item_schema'], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/cancel', [ [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'cancel_item'], 'permission_callback' => [$this, 'cancel_item_permissions_check'], 'args' => [ 'id' => [ 'type' => 'integer', 'required' => true, ], 'trash' => [ 'type' => 'boolean', 'default' => false, 'description' => __('Whether to also move the subscription to trash (trash=true) instead of just canceling it.', 'give'), ], ], 'schema' => [$this, 'get_public_item_schema'], ], ]); } /** * Get subscriptions. * * @since 4.8.0 * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. * * @throws Exception */ public function get_items($request) { $page = $request->get_param('page'); $perPage = $request->get_param('per_page'); $sortColumn = $this->getSortColumn($request->get_param('sort')); $sortDirection = $request->get_param('direction'); $mode = $request->get_param('mode'); $status = $request->get_param('status'); $includeSensitiveData = $request->get_param('includeSensitiveData'); $donorAnonymousMode = new DonorAnonymousMode($request->get_param('anonymousDonors')); $query = new SubscriptionQuery(); $query->whereMode($mode); if ($campaignId = $request->get_param('campaignId')) { $query->whereCampaignId($campaignId); } if ($donorAnonymousMode->isExcluded()) { $query->excludeAnonymousDonors(); } if ($donorId = $request->get_param('donorId')) { $query->whereDonorId($donorId); } if (!in_array('any', (array) $status, true)) { $query->whereStatus((array)$status); } if (in_array($sortColumn, ['firstName', 'lastName'], true)) { $query->selectDonorNames(); } $totalQuery = $query->clone(); $query->limit($perPage)->offset(($page - 1) * $perPage)->orderBy($sortColumn, $sortDirection); $subscriptions = $query->getAll() ?? []; $subscriptions = array_map(function ($subscription) use ($donorAnonymousMode, $includeSensitiveData, $request) { $item = (new SubscriptionViewModel($subscription)) ->anonymousMode($donorAnonymousMode) ->includeSensitiveData($includeSensitiveData) ->exports(); return $this->prepare_response_for_collection( $this->prepare_item_for_response($item, $request) ); }, $subscriptions); $totalSubscriptions = empty($subscriptions) ? 0 : $totalQuery->count(); $response = rest_ensure_response($subscriptions); $response = Headers::addPagination($response, $request, $totalSubscriptions, $perPage, $this->rest_base); return $response; } /** * Get a subscription. * * @since 4.8.0 * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. * * @throws Exception */ public function get_item($request) { $subscription = Subscription::find($request->get_param('id')); if (!$subscription) { return new WP_Error('subscription_not_found', __('Subscription not found', 'give'), ['status' => 404]); } $includeSensitiveData = $request->get_param('includeSensitiveData'); $donorAnonymousMode = new DonorAnonymousMode($request->get_param('anonymousDonors')); $item = (new SubscriptionViewModel($subscription)) ->anonymousMode($donorAnonymousMode) ->includeSensitiveData($includeSensitiveData) ->exports(); $response = $this->prepare_item_for_response($item, $request); return rest_ensure_response($response); } /** * Create a subscription. * * @since 4.8.0 * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. * * @throws Exception */ public function create_item($request) { try { $data = SubscriptionCreateData::fromRequest($request); $subscription = $data->createSubscription(); $fieldsUpdate = $this->update_additional_fields_for_object($subscription, $request); if (is_wp_error($fieldsUpdate)) { return $fieldsUpdate; } } catch (SubscriptionValidationException $e) { return new WP_REST_Response([ 'message' => $e->getMessage(), 'error' => $e->getErrorCode() ], $e->getStatusCode()); } catch (Exception $e) { return new WP_REST_Response([ 'message' => sprintf(__('Failed to create subscription: %s', 'give'), $e->getMessage()), 'error' => 'internal_server_error' ], 500); } $item = (new SubscriptionViewModel($subscription)) ->includeSensitiveData(true) ->exports(); $response = $this->prepare_item_for_response($item, $request); $response->set_status(201); return rest_ensure_response($response); } /** * Update a subscription. * * @since 4.11.0 Exclude gatewaySubscriptionId from non-editable fields * @since 4.8.0 * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. * * @throws Exception */ public function update_item($request) { $subscription = Subscription::find($request->get_param('id')); if (!$subscription) { return new WP_REST_Response(__('Subscription not found', 'give'), 404); } $nonEditableFields = [ 'id', 'createdAt', 'mode', 'gatewayId', ]; foreach ($request->get_params() as $key => $value) { if (!in_array($key, $nonEditableFields, true)) { if (in_array($key, $subscription::propertyKeys(), true)) { try { $processedValue = SubscriptionFields::processValue($key, $value); if ($subscription->isPropertyTypeValid($key, $processedValue)) { $subscription->$key = $processedValue; } } catch (Exception $e) { continue; } } } } if ($subscription->isDirty()) { $subscription->save(); } $fieldsUpdate = $this->update_additional_fields_for_object($subscription, $request); if (is_wp_error($fieldsUpdate)) { return $fieldsUpdate; } $item = (new SubscriptionViewModel($subscription))->includeSensitiveData(true)->exports(); $response = $this->prepare_item_for_response($item, $request); return rest_ensure_response($response); } /** * Delete a subscription. * * @since 4.8.0 * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. * * @throws Exception */ public function delete_item($request): WP_REST_Response { $subscription = Subscription::find($request->get_param('id')); $force = $request->get_param('force'); if (!$subscription) { return new WP_REST_Response(['message' => __('Subscription not found', 'give')], 404); } $item = (new SubscriptionViewModel($subscription))->exports(); if ($force) { // Permanently delete the subscription $deleted = $subscription->delete(); if (!$deleted) { return new WP_REST_Response(['message' => __('Failed to delete subscription', 'give')], 500); } } else { // Move the subscription to trash (soft delete) $trashed = $subscription->trash(); if (!$trashed) { return new WP_REST_Response(['message' => __('Failed to trash subscription', 'give')], 500); } } return new WP_REST_Response(['deleted' => true, 'previous' => $item], 200); } /** * Delete multiple subscriptions. * * @since 4.8.0 * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. * * @throws Exception */ public function deleteItems($request): WP_REST_Response { $ids = $request->get_param('ids'); $force = $request->get_param('force'); $deleted = []; $errors = []; foreach ($ids as $id) { $subscription = Subscription::find($id); if (!$subscription) { $errors[] = ['id' => $id, 'message' => __('Subscription not found', 'give')]; continue; } $item = (new SubscriptionViewModel($subscription))->exports(); if ($force) { if ($subscription->delete()) { $deleted[] = ['id' => $id, 'previous' => $item]; } else { $errors[] = ['id' => $id, 'message' => __('Failed to delete subscription', 'give')]; } } else { $trashed = $subscription->trash(); if ($trashed) { $deleted[] = ['id' => $id, 'previous' => $item]; } else { $errors[] = ['id' => $id, 'message' => __('Failed to trash subscription', 'give')]; } } } return new WP_REST_Response([ 'deleted' => $deleted, 'errors' => $errors, 'total_requested' => count($ids), 'total_deleted' => count($deleted), 'total_errors' => count($errors), ], 200); } /** * Cancel a subscription. * * @since 4.8.0 */ public function cancel_item($request) { $subscription = Subscription::find($request->get_param('id')); if (!$subscription) { return new WP_REST_Response(__('Subscription not found', 'give'), 404); } try { if (give()->gateways->hasPaymentGateway($subscription->gatewayId)) { $subscription->cancel(true); } else { $subscription->status = SubscriptionStatus::CANCELLED(); $subscription->save(); } $trash = $request->get_param('trash'); if ($trash) { $subscription->trash(); } $item = (new SubscriptionViewModel($subscription))->includeSensitiveData(true)->exports(); $response = $this->prepare_item_for_response($item, $request); return rest_ensure_response($response); } catch (Exception $e) { return new WP_REST_Response(__('Failed to cancel subscription', 'give'), 500); } } /** * @since 4.8.0 */ public function getSortColumn(string $sortColumn): string { $sortColumnsMap = [ 'id' => 'id', 'createdAt' => 'created', 'renewsAt' => 'expiration', 'status' => 'status', 'amount' => 'recurring_amount', 'feeAmountRecovered' => 'recurring_fee_amount', 'donorId' => 'customer_id', 'firstName' => 'firstName', 'lastName' => 'lastName', ]; return $sortColumnsMap[$sortColumn] ?? 'id'; } /** * @since 4.8.0 */ public function get_collection_params(): array { $params = parent::get_collection_params(); $params['page']['default'] = 1; $params['per_page']['default'] = 30; // Remove default parameters not being used unset($params['context']); unset($params['search']); $params += give(GetSubscriptionCollectionParams::class)(); return $params; } /** * @since 4.13.0 added anonymousDonors and includeSensitiveData to embeddable links * @since 4.10.0 added embeddable links for campaign and form * @since 4.8.0 * * @param mixed $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) { try { $subscriptionId = $request->get_param('id') ?? $item['id'] ?? null; if ($subscriptionId && $subscription = Subscription::find($subscriptionId)) { $self_url = rest_url(sprintf('%s/%s/%d', $this->namespace, $this->rest_base, $subscription->id)); $links = [ 'self' => ['href' => $self_url] ]; if (!empty($item['donorId'])) { $donor_url = rest_url(sprintf('%s/%s/%d', $this->namespace, 'donors', $item['donorId'])); $donor_url = add_query_arg([ 'mode' => $request->get_param('mode'), 'anonymousDonors' => $request->get_param('anonymousDonors'), 'includeSensitiveData' => $request->get_param('includeSensitiveData'), ], $donor_url); $links[CURIE::relationUrl('donor')] = [ 'href' => $donor_url, 'embeddable' => true, ]; } if (!empty($item['donationFormId'])) { $form_url = rest_url(sprintf('%s/%s/%d', $this->namespace, 'forms', $item['donationFormId'])); $form_url = add_query_arg([ 'mode' => $subscription->mode->getValue(), ], $form_url); $links[CURIE::relationUrl('form')] = [ 'href' => $form_url, 'embeddable' => true, ]; } if (!empty($item['campaignId'])) { $campaign_url = rest_url(sprintf('%s/%s/%d', $this->namespace, 'campaigns', $item['campaignId'])); $campaign_url = add_query_arg([ 'mode' => $subscription->mode->getValue(), ], $campaign_url); $links[CURIE::relationUrl('campaign')] = [ 'href' => $campaign_url, 'embeddable' => true, ]; } $donations_url = rest_url(sprintf('%s/%s', $this->namespace, 'donations')); $donations_url = add_query_arg([ 'mode' => $subscription->mode->getValue(), 'subscriptionId' => $subscription->id, 'anonymousDonations' => $request->get_param('anonymousDonors'), 'includeSensitiveData' => $request->get_param('includeSensitiveData'), ], $donations_url); $links[CURIE::relationUrl('donations')] = [ 'href' => $donations_url, 'embeddable' => true, ]; } else { $links = []; } $response = new WP_REST_Response(Item::formatDatesForResponse($item, ['createdAt', 'renewsAt'])); if (!empty($links)) { $response->add_links($links); } $response->data = $this->add_additional_fields_to_object($response->data, $request); return $response; } catch (Exception $e) { return new WP_Error( 'prepare_item_for_response_error', sprintf( __('Error while preparing subscription for response: %s', 'give'), $e->getMessage() ), ['status' => 400] ); } } /** * @since 4.8.0 * * @param WP_REST_Request $request * * @return true|WP_Error */ public function get_items_permissions_check($request) { return SubscriptionPermissions::validationForGetMethods($request); } /** * @since 4.8.0 * * @param WP_REST_Request $request * * @return true|WP_Error */ public function get_item_permissions_check($request) { return SubscriptionPermissions::validationForGetMethods($request); } /** * @since 4.8.0 * * @param WP_REST_Request $request * * @return true|WP_Error */ public function update_item_permissions_check($request) { return SubscriptionPermissions::validationForUpdateMethod($request); } /** * @since 4.8.0 * * @param WP_REST_Request $request * * @return true|WP_Error */ public function create_item_permissions_check($request) { return SubscriptionPermissions::validationForUpdateMethod($request); } /** * @since 4.8.0 * * @param WP_REST_Request $request * * @return true|WP_Error */ public function delete_item_permissions_check($request) { return SubscriptionPermissions::validationForDeleteMethods($request); } /** * @since 4.8.0 * * @param WP_REST_Request $request * * @return true|WP_Error */ public function delete_items_permissions_check($request) { return SubscriptionPermissions::validationForDeleteMethods($request); } /** * @since 4.8.0 */ public function cancel_item_permissions_check($request) { return SubscriptionPermissions::validationForDeleteMethods($request); } /** * @since 4.8.0 */ public function get_item_schema(): array { $schema = give(GetSubscriptionItemSchema::class)(); return $this->add_additional_fields_schema($schema); } }