diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php new file mode 100644 index 0000000000..03913e9354 --- /dev/null +++ b/src/Donations/Controllers/DonationRequestController.php @@ -0,0 +1,91 @@ +<?php + +namespace Give\Donations\Controllers; + +use Give\Donations\Models\Donation; +use Give\Donations\Repositories\DonationRepository; +use Give\Donations\ValueObjects\DonationMetaKeys; +use Give\Donations\ValueObjects\DonationRoute; +use WP_Error; +use WP_REST_Request; +use WP_REST_Response; + +/** + * @unreleased + */ +class DonationRequestController +{ + /** + * @unreleased + * + * @return WP_Error | WP_REST_Response + */ + public function getDonation(WP_REST_Request $request) + { + $donation = Donation::find($request->get_param('id')); + + if ( ! $donation) { + return new WP_Error('donation_not_found', __('Donation not found', 'give'), ['status' => 404]); + } + + return new WP_REST_Response($donation->toArray()); + } + + /** + * @unreleased + */ + public function getDonations(WP_REST_Request $request): WP_REST_Response + { + $campaignId = $request->get_param('campaignId'); + $page = $request->get_param('page'); + $perPage = $request->get_param('per_page'); + + $query = give(DonationRepository::class)->prepareQuery(); + + if ($campaignId) { + $metaKey = DonationMetaKeys::CAMPAIGN_ID; + $query->attachMeta('give_donationmeta', 'ID', 'donation_id', $metaKey) + ->where("give_donationmeta_attach_meta_{$metaKey}.meta_value", $campaignId); + } + + $query + ->limit($perPage) + ->offset(($page - 1) * $perPage); + + $donations = $query->getAll() ?? []; + $totalDonations = empty($donations) ? 0 : Donation::query()->count(); + $totalPages = (int)ceil($totalDonations / $perPage); + + $response = rest_ensure_response($donations); + $response->header('X-WP-Total', $totalDonations); + $response->header('X-WP-TotalPages', $totalPages); + + $base = add_query_arg( + map_deep($request->get_query_params(), function ($value) { + if (is_bool($value)) { + $value = $value ? 'true' : 'false'; + } + + return urlencode($value); + }), + rest_url(DonationRoute::DONATIONS) + ); + + if ($page > 1) { + $prevPage = $page - 1; + + if ($prevPage > $totalPages) { + $prevPage = $totalPages; + } + + $response->link_header('prev', add_query_arg('page', $prevPage, $base)); + } + + if ($totalPages > $page) { + $nextPage = $page + 1; + $response->link_header('next', add_query_arg('page', $nextPage, $base)); + } + + return $response; + } +} diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php new file mode 100644 index 0000000000..29a8a46da4 --- /dev/null +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -0,0 +1,106 @@ +<?php + +namespace Give\Donations\Routes; + +use Give\Donations\Controllers\DonationRequestController; +use Give\Donations\ValueObjects\DonationRoute; +use WP_REST_Request; +use WP_REST_Server; + +/** + * @unreleased + */ +class RegisterDonationRoutes +{ + /** + * @var DonationRequestController + */ + protected $donationRequestController; + + /** + * @unreleased + */ + public function __construct(DonationRequestController $donationRequestController) + { + $this->donationRequestController = $donationRequestController; + } + + /** + * @unreleased + */ + public function __invoke() + { + $this->registerGetDonation(); + $this->registerGetDonations(); + } + + /** + * Get Donation route + * + * @unreleased + */ + public function registerGetDonation() + { + register_rest_route( + DonationRoute::NAMESPACE, + DonationRoute::DONATION, + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => function (WP_REST_Request $request) { + return $this->donationRequestController->getDonation($request); + }, + 'permission_callback' => function () { + return current_user_can('manage_options'); + }, + ], + 'args' => [ + 'id' => [ + 'type' => 'integer', + 'required' => true, + ], + ], + ] + ); + } + + /** + * Get Donations route + * + * @unreleased + */ + public function registerGetDonations() + { + register_rest_route( + DonationRoute::NAMESPACE, + DonationRoute::DONATIONS, + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => function (WP_REST_Request $request) { + return $this->donationRequestController->getDonations($request); + }, + 'permission_callback' => '__return_true', + ], + 'args' => [ + 'page' => [ + 'type' => 'integer', + 'default' => 1, + 'minimum' => 1, + ], + 'per_page' => [ + 'type' => 'integer', + 'default' => 30, + 'minimum' => 1, + 'maximum' => 100, + ], + 'campaignId' => [ + 'type' => 'integer', + 'required' => false, + 'default' => 0, + ], + ], + ] + ); + } +} diff --git a/src/Donations/ServiceProvider.php b/src/Donations/ServiceProvider.php index c7b826baff..c9930ee2e9 100644 --- a/src/Donations/ServiceProvider.php +++ b/src/Donations/ServiceProvider.php @@ -56,6 +56,8 @@ public function boot() MoveDonationCommentToDonationMetaTable::class, UnserializeTitlePrefix::class, ]); + + $this->registerRoutes(); } /** @@ -129,4 +131,12 @@ private function addCustomFieldsToDonationDetails() echo (new DonationDetailsController())->show($donationId); }); } + + /** + * @unreleased + */ + private function registerRoutes() + { + Hooks::addAction('rest_api_init', Routes\RegisterDonationRoutes::class); + } } diff --git a/src/Donations/ValueObjects/DonationRoute.php b/src/Donations/ValueObjects/DonationRoute.php new file mode 100644 index 0000000000..13e7add090 --- /dev/null +++ b/src/Donations/ValueObjects/DonationRoute.php @@ -0,0 +1,22 @@ +<?php + +namespace Give\Donations\ValueObjects; + +use Give\Framework\Support\ValueObjects\Enum; + +/** + * @unreleased + * + * @method static DonationRoute NAMESPACE() + * @method static DonationRoute DONATION() + * @method static DonationRoute DONATIONS() + * @method bool isNamespace() + * @method bool isDonation() + * @method bool isDonations() + */ +class DonationRoute extends Enum +{ + const NAMESPACE = 'give-api/v2'; + const DONATION = 'donations/(?P<id>[0-9]+)'; + const DONATIONS = 'donations'; +} diff --git a/tests/Unit/Donations/Routes/GetDonationsTest.php b/tests/Unit/Donations/Routes/GetDonationsTest.php new file mode 100644 index 0000000000..9baa536ae4 --- /dev/null +++ b/tests/Unit/Donations/Routes/GetDonationsTest.php @@ -0,0 +1,51 @@ +<?php + +namespace Unit\Donations\Routes; + +use Exception; +use Give\Campaigns\Models\Campaign; +use Give\Donations\Models\Donation; +use Give\Donations\ValueObjects\DonationMetaKeys; +use Give\Donations\ValueObjects\DonationRoute; +use Give\Tests\RestApiTestCase; +use Give\Tests\TestTraits\RefreshDatabase; +use WP_REST_Request; +use WP_REST_Server; + +/** + * @unreleased + */ +class GetDonationsTest extends RestApiTestCase +{ + use RefreshDatabase; + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsByCampaignId() + { + /** @var Campaign $campaign */ + $campaign = Campaign::factory()->create(); + + /** @var Donation $donation */ + $donation = Donation::factory()->create(); + give()->payment_meta->update_meta($donation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + + $route = '/' . DonationRoute::NAMESPACE . '/' . DonationRoute::DONATIONS; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'campaignId' => $campaign->id, + ] + ); + + $response = $this->dispatchRequest($request); + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals($donation->id, $data[0]->id); + } +}