From e22047b53fa59704647cd1fab9ce134d80048907 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Tue, 28 Jan 2025 11:52:21 -0300 Subject: [PATCH 01/43] feature: add get routes for donations --- .../Controllers/DonationRequestController.php | 91 +++++++++++++++ .../Routes/RegisterDonationRoutes.php | 106 ++++++++++++++++++ src/Donations/ServiceProvider.php | 10 ++ src/Donations/ValueObjects/DonationRoute.php | 22 ++++ .../Donations/Routes/GetDonationsTest.php | 51 +++++++++ 5 files changed, 280 insertions(+) create mode 100644 src/Donations/Controllers/DonationRequestController.php create mode 100644 src/Donations/Routes/RegisterDonationRoutes.php create mode 100644 src/Donations/ValueObjects/DonationRoute.php create mode 100644 tests/Unit/Donations/Routes/GetDonationsTest.php 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 @@ +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 @@ +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 @@ +[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 @@ +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); + } +} From a2d269528e400dcae119dce0cf4c714fe1b3bdba Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Tue, 28 Jan 2025 12:58:10 -0300 Subject: [PATCH 02/43] tests: add test for donations pagination --- .../Donations/Routes/GetDonationsTest.php | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Donations/Routes/GetDonationsTest.php b/tests/Unit/Donations/Routes/GetDonationsTest.php index 9baa536ae4..85fc651362 100644 --- a/tests/Unit/Donations/Routes/GetDonationsTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsTest.php @@ -19,6 +19,61 @@ class GetDonationsTest extends RestApiTestCase { use RefreshDatabase; + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsPagination() + { + Donation::query()->delete(); + + /** @var Donation $donation */ + $donation1 = Donation::factory()->create(); + + /** @var Donation $donation */ + $donation2 = Donation::factory()->create(); + + $route = '/' . DonationRoute::NAMESPACE . '/' . DonationRoute::DONATIONS; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 1, + ] + ); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + $headers = $response->get_headers(); + + $this->assertEquals(200, $status); + $this->assertEquals(1, count($data)); + $this->assertEquals($donation1->id, $data[0]->id); + $this->assertEquals(2, $headers['X-WP-Total']); + $this->assertEquals(2, $headers['X-WP-TotalPages']); + + $request->set_query_params( + [ + 'page' => 2, + 'per_page' => 1, + ] + ); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + $headers = $response->get_headers(); + + $this->assertEquals(200, $status); + $this->assertEquals(1, count($data)); + $this->assertEquals($donation2->id, $data[0]->id); + $this->assertEquals(2, $headers['X-WP-Total']); + $this->assertEquals(2, $headers['X-WP-TotalPages']); + } + /** * @unreleased * @@ -40,8 +95,8 @@ public function testGetDonationsByCampaignId() 'campaignId' => $campaign->id, ] ); - $response = $this->dispatchRequest($request); + $status = $response->get_status(); $data = $response->get_data(); From 74af7060e379046a514d9040e8d6e07547b24b82 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Tue, 28 Jan 2025 13:28:35 -0300 Subject: [PATCH 03/43] tests: add more asserts --- tests/Unit/Donations/Routes/GetDonationsTest.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Donations/Routes/GetDonationsTest.php b/tests/Unit/Donations/Routes/GetDonationsTest.php index 85fc651362..f68f3c33ed 100644 --- a/tests/Unit/Donations/Routes/GetDonationsTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsTest.php @@ -85,8 +85,12 @@ public function testGetDonationsByCampaignId() $campaign = Campaign::factory()->create(); /** @var Donation $donation */ - $donation = Donation::factory()->create(); - give()->payment_meta->update_meta($donation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + $donation1 = Donation::factory()->create(); + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + + /** @var Donation $donation */ + $donation2 = Donation::factory()->create(); + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); $route = '/' . DonationRoute::NAMESPACE . '/' . DonationRoute::DONATIONS; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -101,6 +105,8 @@ public function testGetDonationsByCampaignId() $data = $response->get_data(); $this->assertEquals(200, $status); - $this->assertEquals($donation->id, $data[0]->id); + $this->assertEquals(2, count($data)); + $this->assertEquals($donation1->id, $data[0]->id); + $this->assertEquals($donation2->id, $data[1]->id); } } From ebd4d575bdfd19eb0420eb8aca7217e024efc8f9 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Tue, 28 Jan 2025 14:07:38 -0300 Subject: [PATCH 04/43] tests: add GetDonationRouteTest class --- .../Routes/RegisterDonationRoutes.php | 2 +- .../Donations/Routes/GetDonationRouteTest.php | 39 +++++++++++++++++++ ...ionsTest.php => GetDonationsRouteTest.php} | 8 ++-- 3 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/Donations/Routes/GetDonationRouteTest.php rename tests/Unit/Donations/Routes/{GetDonationsTest.php => GetDonationsRouteTest.php} (92%) diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index 29a8a46da4..a71d550777 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -51,7 +51,7 @@ public function registerGetDonation() return $this->donationRequestController->getDonation($request); }, 'permission_callback' => function () { - return current_user_can('manage_options'); + return true; //current_user_can('manage_options'); }, ], 'args' => [ diff --git a/tests/Unit/Donations/Routes/GetDonationRouteTest.php b/tests/Unit/Donations/Routes/GetDonationRouteTest.php new file mode 100644 index 0000000000..48c3e458fc --- /dev/null +++ b/tests/Unit/Donations/Routes/GetDonationRouteTest.php @@ -0,0 +1,39 @@ +create(); + + $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals($donation->id, $data['id']); + } +} diff --git a/tests/Unit/Donations/Routes/GetDonationsTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php similarity index 92% rename from tests/Unit/Donations/Routes/GetDonationsTest.php rename to tests/Unit/Donations/Routes/GetDonationsRouteTest.php index f68f3c33ed..367c4bc832 100644 --- a/tests/Unit/Donations/Routes/GetDonationsTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -15,7 +15,7 @@ /** * @unreleased */ -class GetDonationsTest extends RestApiTestCase +class GetDonationsRouteTest extends RestApiTestCase { use RefreshDatabase; @@ -24,7 +24,7 @@ class GetDonationsTest extends RestApiTestCase * * @throws Exception */ - public function testGetDonationsPagination() + public function testGetDonationsWithPagination() { Donation::query()->delete(); @@ -34,7 +34,7 @@ public function testGetDonationsPagination() /** @var Donation $donation */ $donation2 = Donation::factory()->create(); - $route = '/' . DonationRoute::NAMESPACE . '/' . DonationRoute::DONATIONS; + $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( @@ -92,7 +92,7 @@ public function testGetDonationsByCampaignId() $donation2 = Donation::factory()->create(); give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - $route = '/' . DonationRoute::NAMESPACE . '/' . DonationRoute::DONATIONS; + $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( [ From 6996169134483e8e07e8b2d850a5679e548dfcc2 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Wed, 29 Jan 2025 13:36:17 -0300 Subject: [PATCH 05/43] feature: add get endpoints for donors --- .../Controllers/DonationRequestController.php | 14 ++- .../Controllers/DonorRequestController.php | 105 +++++++++++++++++ src/Donors/Routes/RegisterDonorRoutes.php | 107 ++++++++++++++++++ src/Donors/ServiceProvider.php | 10 ++ src/Donors/ValueObjects/DonorRoute.php | 22 ++++ .../Unit/Donors/Routes/GetDonorsRouteTest.php | 66 +++++++++++ 6 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 src/Donors/Controllers/DonorRequestController.php create mode 100644 src/Donors/Routes/RegisterDonorRoutes.php create mode 100644 src/Donors/ValueObjects/DonorRoute.php create mode 100644 tests/Unit/Donors/Routes/GetDonorsRouteTest.php diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 03913e9354..c011be71e2 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -4,8 +4,8 @@ use Give\Donations\Models\Donation; use Give\Donations\Repositories\DonationRepository; -use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donations\ValueObjects\DonationRoute; +use Give\Framework\QueryBuilder\JoinQueryBuilder; use WP_Error; use WP_REST_Request; use WP_REST_Response; @@ -36,16 +36,20 @@ public function getDonation(WP_REST_Request $request) */ 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; + if ($campaignId = $request->get_param('campaignId')) { + /*$metaKey = DonationMetaKeys::CAMPAIGN_ID; $query->attachMeta('give_donationmeta', 'ID', 'donation_id', $metaKey) - ->where("give_donationmeta_attach_meta_{$metaKey}.meta_value", $campaignId); + ->where("give_donationmeta_attach_meta_{$metaKey}.meta_value", $campaignId)*/ + + $query->distinct()->join(function (JoinQueryBuilder $builder) { + $builder->leftJoin('give_campaign_forms', 'campaign_forms') + ->on('campaign_forms.form_id', "give_donationmeta_attach_meta_formId.meta_value"); + })->where('campaign_forms.campaign_id', $campaignId); } $query diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php new file mode 100644 index 0000000000..b414789648 --- /dev/null +++ b/src/Donors/Controllers/DonorRequestController.php @@ -0,0 +1,105 @@ +get_param('id')); + + if ( ! $donation) { + return new WP_Error('donor_not_found', __('Donation not found', 'give'), ['status' => 404]); + } + + return new WP_REST_Response($donation->toArray()); + } + + /** + * @unreleased + */ + public function getDonors(WP_REST_Request $request): WP_REST_Response + { + $page = $request->get_param('page'); + $perPage = $request->get_param('per_page'); + + $query = give(DonorRepository::class)->prepareQuery(); + + + if ($campaignId = $request->get_param('campaignId')) { + $query->select(['donationmeta1.donation_id', 'donationId']) + ->distinct() + ->join(function (JoinQueryBuilder $builder) use ($campaignId) { + $builder->innerJoin('give_donationmeta', 'donationmeta1') + ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); + + $builder->innerJoin('give_donationmeta', 'donationmeta2') + ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::CAMPAIGN_ID . "' AND donationmeta2.meta_value = {$campaignId}"); + })->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { + $builder + ->select('ID') + ->from('posts') + ->whereRaw("WHERE ID = donationmeta1.donation_id AND post_type = 'give_payment' AND post_status = 'publish'"); + }); + } + + $query + ->limit($perPage) + ->offset(($page - 1) * $perPage); + + $donors = $query->getAll() ?? []; + $totalDonors = empty($donors) ? 0 : Donation::query()->count(); + $totalPages = (int)ceil($totalDonors / $perPage); + + $response = rest_ensure_response($donors); + $response->header('X-WP-Total', $totalDonors); + $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/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php new file mode 100644 index 0000000000..fa6272f422 --- /dev/null +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -0,0 +1,107 @@ +donorRequestController = $donorRequestController; + } + + /** + * @unreleased + */ + public function __invoke() + { + $this->registerGetDonor(); + $this->registerGetDonors(); + } + + /** + * Get Donor route + * + * @unreleased + */ + public function registerGetDonor() + { + register_rest_route( + DonorRoute::NAMESPACE, + DonorRoute::DONOR, + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => function (WP_REST_Request $request) { + return $this->donorRequestController->getDonor($request); + }, + 'permission_callback' => function () { + return true; //current_user_can('manage_options'); + }, + ], + 'args' => [ + 'id' => [ + 'type' => 'integer', + 'required' => true, + ], + ], + ] + ); + } + + /** + * Get Donor route + * + * @unreleased + */ + public function registerGetDonors() + { + register_rest_route( + DonorRoute::NAMESPACE, + DonorRoute::DONORS, + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => function (WP_REST_Request $request) { + return $this->donorRequestController->getDonors($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/Donors/ServiceProvider.php b/src/Donors/ServiceProvider.php index 74b6d938e5..552357b14c 100644 --- a/src/Donors/ServiceProvider.php +++ b/src/Donors/ServiceProvider.php @@ -66,6 +66,8 @@ public function boot() ]); Hooks::addAction('give_admin_donor_details_updating', UpdateAdminDonorDetails::class, '__invoke', 10, 2); + + $this->registerRoutes(); } /** @@ -104,4 +106,12 @@ protected function enforceDonorsAsUsers() } }, 10, 2); } + + /** + * @unreleased + */ + private function registerRoutes() + { + Hooks::addAction('rest_api_init', Routes\RegisterDonorRoutes::class); + } } diff --git a/src/Donors/ValueObjects/DonorRoute.php b/src/Donors/ValueObjects/DonorRoute.php new file mode 100644 index 0000000000..804e089286 --- /dev/null +++ b/src/Donors/ValueObjects/DonorRoute.php @@ -0,0 +1,22 @@ +[0-9]+)'; + const DONORS = 'donors'; +} diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php new file mode 100644 index 0000000000..30836b8144 --- /dev/null +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -0,0 +1,66 @@ +delete(); + + Donation::query()->delete(); + + /** @var Campaign $campaign */ + $campaign = Campaign::factory()->create(); + + /** @var Donation $donation1 */ + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donor1 = $donation1->donor; + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::DONOR_ID, $donor1->id); + + /** @var Donation $donation2 */ + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donor2 = $donation2->donor; + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::DONOR_ID, $donor2->id); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $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(2, count($data)); + $this->assertEquals($donor1->id, $data[0]->id); + $this->assertEquals($donor2->id, $data[1]->id); + } +} From aaecb8ab7996fdc45f574ebb7d770bef4901d251 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Wed, 29 Jan 2025 13:49:25 -0300 Subject: [PATCH 06/43] tests: add GetDonorRouteTest class --- .../Controllers/DonationRequestController.php | 4 --- .../Routes/RegisterDonationRoutes.php | 4 +-- .../Controllers/DonorRequestController.php | 1 - .../Unit/Donors/Routes/GetDonorRouteTest.php | 36 +++++++++++++++++++ 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 tests/Unit/Donors/Routes/GetDonorRouteTest.php diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index c011be71e2..e1e690224e 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -42,10 +42,6 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response $query = give(DonationRepository::class)->prepareQuery(); if ($campaignId = $request->get_param('campaignId')) { - /*$metaKey = DonationMetaKeys::CAMPAIGN_ID; - $query->attachMeta('give_donationmeta', 'ID', 'donation_id', $metaKey) - ->where("give_donationmeta_attach_meta_{$metaKey}.meta_value", $campaignId)*/ - $query->distinct()->join(function (JoinQueryBuilder $builder) { $builder->leftJoin('give_campaign_forms', 'campaign_forms') ->on('campaign_forms.form_id', "give_donationmeta_attach_meta_formId.meta_value"); diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index a71d550777..d71912a467 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -50,9 +50,7 @@ public function registerGetDonation() 'callback' => function (WP_REST_Request $request) { return $this->donationRequestController->getDonation($request); }, - 'permission_callback' => function () { - return true; //current_user_can('manage_options'); - }, + 'permission_callback' => '__return_true', ], 'args' => [ 'id' => [ diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index b414789648..ec60429f23 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -44,7 +44,6 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response $query = give(DonorRepository::class)->prepareQuery(); - if ($campaignId = $request->get_param('campaignId')) { $query->select(['donationmeta1.donation_id', 'donationId']) ->distinct() diff --git a/tests/Unit/Donors/Routes/GetDonorRouteTest.php b/tests/Unit/Donors/Routes/GetDonorRouteTest.php new file mode 100644 index 0000000000..2843a1b01a --- /dev/null +++ b/tests/Unit/Donors/Routes/GetDonorRouteTest.php @@ -0,0 +1,36 @@ +create(); + + $route = '/' . DonationRoute::NAMESPACE . '/donors/' . $donor->id; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals($donor->id, $data['id']); + } +} From 75053884eaed021f84553f718615368b72e8bdce Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Wed, 29 Jan 2025 14:19:45 -0300 Subject: [PATCH 07/43] tests: add testGetDonorsWithPagination method --- .../Controllers/DonorRequestController.php | 5 +- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 58 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index ec60429f23..09e4744dbf 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -2,7 +2,6 @@ namespace Give\Donors\Controllers; -use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donations\ValueObjects\DonationRoute; use Give\Donors\Models\Donor; @@ -65,8 +64,10 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response ->limit($perPage) ->offset(($page - 1) * $perPage); + $sql = $query->getSQL(); + $donors = $query->getAll() ?? []; - $totalDonors = empty($donors) ? 0 : Donation::query()->count(); + $totalDonors = empty($donors) ? 0 : Donor::query()->count(); $totalPages = (int)ceil($totalDonors / $perPage); $response = rest_ensure_response($donors); diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 30836b8144..72601b3e58 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -7,7 +7,9 @@ use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donations\ValueObjects\DonationStatus; +use Give\Donors\Models\Donor; use Give\Donors\ValueObjects\DonorRoute; +use Give\Framework\Database\DB; use Give\Tests\RestApiTestCase; use Give\Tests\TestTraits\RefreshDatabase; use WP_REST_Request; @@ -25,10 +27,62 @@ class GetDonorsRouteTest extends RestApiTestCase * * @throws Exception */ - public function testGetDonorsByCampaignId() + public function testGetDonorsWithPagination() { - //Donor::query()->delete(); + DB::query("DELETE FROM " . DB::prefix('give_donors')); + + /** @var Donor $donor1 */ + $donor1 = Donor::factory()->create(); + + /** @var Donor $donor2 */ + $donor2 = Donor::factory()->create(); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 1, + ] + ); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + $headers = $response->get_headers(); + + $this->assertEquals(200, $status); + $this->assertEquals(1, count($data)); + $this->assertEquals($donor1->id, $data[0]->id); + $this->assertEquals(2, $headers['X-WP-Total']); + $this->assertEquals(2, $headers['X-WP-TotalPages']); + $request->set_query_params( + [ + 'page' => 2, + 'per_page' => 1, + ] + ); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + $headers = $response->get_headers(); + + $this->assertEquals(200, $status); + $this->assertEquals(1, count($data)); + $this->assertEquals($donor2->id, $data[0]->id); + $this->assertEquals(2, $headers['X-WP-Total']); + $this->assertEquals(2, $headers['X-WP-TotalPages']); + } + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsByCampaignId() + { Donation::query()->delete(); /** @var Campaign $campaign */ From 0850304b21355ecdb56256683f2fc180d23e4d1b Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Wed, 29 Jan 2025 14:23:07 -0300 Subject: [PATCH 08/43] refactor: change permissions callback --- src/Donors/Routes/RegisterDonorRoutes.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index fa6272f422..22e9ba92f4 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -51,9 +51,7 @@ public function registerGetDonor() 'callback' => function (WP_REST_Request $request) { return $this->donorRequestController->getDonor($request); }, - 'permission_callback' => function () { - return true; //current_user_can('manage_options'); - }, + 'permission_callback' => '__return_true', ], 'args' => [ 'id' => [ From df1697bd4c9b4e89954ba22bf12831ada4676edc Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Wed, 29 Jan 2025 15:27:47 -0300 Subject: [PATCH 09/43] refactor: tweaks and polishments --- src/Donations/Controllers/DonationRequestController.php | 2 +- src/Donors/Controllers/DonorRequestController.php | 4 +--- src/Donors/Routes/RegisterDonorRoutes.php | 5 ++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index e1e690224e..6dc49e0443 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -43,7 +43,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response if ($campaignId = $request->get_param('campaignId')) { $query->distinct()->join(function (JoinQueryBuilder $builder) { - $builder->leftJoin('give_campaign_forms', 'campaign_forms') + $builder->innerJoin('give_campaign_forms', 'campaign_forms') ->on('campaign_forms.form_id', "give_donationmeta_attach_meta_formId.meta_value"); })->where('campaign_forms.campaign_id', $campaignId); } diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 09e4744dbf..d7f44c9bf5 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -27,7 +27,7 @@ public function getDonor(WP_REST_Request $request) $donation = Donor::find($request->get_param('id')); if ( ! $donation) { - return new WP_Error('donor_not_found', __('Donation not found', 'give'), ['status' => 404]); + return new WP_Error('donor_not_found', __('Donor not found', 'give'), ['status' => 404]); } return new WP_REST_Response($donation->toArray()); @@ -64,8 +64,6 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response ->limit($perPage) ->offset(($page - 1) * $perPage); - $sql = $query->getSQL(); - $donors = $query->getAll() ?? []; $totalDonors = empty($donors) ? 0 : Donor::query()->count(); $totalPages = (int)ceil($totalDonors / $perPage); diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index 22e9ba92f4..ee4bc77465 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -2,7 +2,6 @@ namespace Give\Donors\Routes; -use Give\Donations\Controllers\DonationRequestController; use Give\Donors\Controllers\DonorRequestController; use Give\Donors\ValueObjects\DonorRoute; use WP_REST_Request; @@ -14,7 +13,7 @@ class RegisterDonorRoutes { /** - * @var DonationRequestController + * @var DonorRequestController */ protected $donorRequestController; @@ -64,7 +63,7 @@ public function registerGetDonor() } /** - * Get Donor route + * Get Donors route * * @unreleased */ From a1900dd1ed1954c7378f5cd4a6cf3736fae1773a Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Wed, 29 Jan 2025 15:40:59 -0300 Subject: [PATCH 10/43] tests: tweak property comments --- tests/Unit/Donations/Routes/GetDonationsRouteTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 367c4bc832..6776fc7921 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -28,10 +28,10 @@ public function testGetDonationsWithPagination() { Donation::query()->delete(); - /** @var Donation $donation */ + /** @var Donation $donation1 */ $donation1 = Donation::factory()->create(); - /** @var Donation $donation */ + /** @var Donation $donation2 */ $donation2 = Donation::factory()->create(); $route = '/' . DonationRoute::NAMESPACE . '/donations'; @@ -84,11 +84,11 @@ public function testGetDonationsByCampaignId() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - /** @var Donation $donation */ + /** @var Donation $donation1 */ $donation1 = Donation::factory()->create(); give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - /** @var Donation $donation */ + /** @var Donation $donation2 */ $donation2 = Donation::factory()->create(); give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); From 5a1bb94bf40586c1e431690634aba1bc78352e88 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Wed, 29 Jan 2025 15:49:03 -0300 Subject: [PATCH 11/43] fix: wrong rest_url path --- src/Donors/Controllers/DonorRequestController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index d7f44c9bf5..8df53a2789 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -3,9 +3,9 @@ namespace Give\Donors\Controllers; use Give\Donations\ValueObjects\DonationMetaKeys; -use Give\Donations\ValueObjects\DonationRoute; use Give\Donors\Models\Donor; use Give\Donors\Repositories\DonorRepository; +use Give\Donors\ValueObjects\DonorRoute; use Give\Framework\QueryBuilder\JoinQueryBuilder; use Give\Framework\QueryBuilder\QueryBuilder; use WP_Error; @@ -80,7 +80,7 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response return urlencode($value); }), - rest_url(DonationRoute::DONATIONS) + rest_url(DonorRoute::DONORS) ); if ($page > 1) { From bfe2a6f230457998b764cb26b72e84ccf0dafc44 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 09:58:58 -0300 Subject: [PATCH 12/43] refactor: restrict access to sensitive data --- src/Campaigns/ServiceProvider.php | 2 +- .../Controllers/DonationRequestController.php | 28 ++++++++++++++- .../Controllers/DonorRequestController.php | 32 +++++++++++++++-- .../Donations/Routes/GetDonationRouteTest.php | 29 +++++++++++++++ .../Routes/GetDonationsRouteTest.php | 36 ++++++++++++++++--- .../Unit/Donors/Routes/GetDonorRouteTest.php | 29 +++++++++++++++ .../Unit/Donors/Routes/GetDonorsRouteTest.php | 36 ++++++++++++++++--- 7 files changed, 179 insertions(+), 13 deletions(-) diff --git a/src/Campaigns/ServiceProvider.php b/src/Campaigns/ServiceProvider.php index c6c7f6282c..5ab3abf39b 100644 --- a/src/Campaigns/ServiceProvider.php +++ b/src/Campaigns/ServiceProvider.php @@ -142,7 +142,7 @@ private function setupCampaignForms() * @see https://github.com/impress-org/givewp/pull/7483 */ if ( ! defined('GIVE_IS_ALL_STATS_COLUMNS_ASYNC_ON_ADMIN_FORM_LIST_VIEWS')) { - define('GIVE_IS_ALL_STATS_COLUMNS_ASYNC_ON_ADMIN_FORM_LIST_VIEWS', false); + //define('GIVE_IS_ALL_STATS_COLUMNS_ASYNC_ON_ADMIN_FORM_LIST_VIEWS', false); } Hooks::addAction('save_post_give_forms', AddCampaignFormFromRequest::class, 'optionBasedFormEditor', 10, 3); diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 6dc49e0443..58a00533bd 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -28,7 +28,7 @@ public function getDonation(WP_REST_Request $request) return new WP_Error('donation_not_found', __('Donation not found', 'give'), ['status' => 404]); } - return new WP_REST_Response($donation->toArray()); + return new WP_REST_Response($this->escDonation($donation)); } /** @@ -53,6 +53,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response ->offset(($page - 1) * $perPage); $donations = $query->getAll() ?? []; + $donations = array_map([$this, 'escDonation'], $donations); $totalDonations = empty($donations) ? 0 : Donation::query()->count(); $totalPages = (int)ceil($totalDonations / $perPage); @@ -88,4 +89,29 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response return $response; } + + /** + * @unreleased + */ + public function escDonation(Donation $donation): array + { + $donation = $donation->toArray(); + + if ( ! current_user_can('manage_options')) { + $sensitiveProperties = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; + + foreach ($sensitiveProperties as $property) { + if (array_key_exists($property, $donation)) { + unset($donation[$property]); + } + } + } + + return $donation; + } } diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 8df53a2789..f0b576527f 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -24,13 +24,13 @@ class DonorRequestController */ public function getDonor(WP_REST_Request $request) { - $donation = Donor::find($request->get_param('id')); + $donor = Donor::find($request->get_param('id')); - if ( ! $donation) { + if ( ! $donor) { return new WP_Error('donor_not_found', __('Donor not found', 'give'), ['status' => 404]); } - return new WP_REST_Response($donation->toArray()); + return new WP_REST_Response($this->escDonor($donor)); } /** @@ -65,6 +65,7 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response ->offset(($page - 1) * $perPage); $donors = $query->getAll() ?? []; + $donors = array_map([$this, 'escDonor'], $donors); $totalDonors = empty($donors) ? 0 : Donor::query()->count(); $totalPages = (int)ceil($totalDonors / $perPage); @@ -100,4 +101,29 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response return $response; } + + /** + * @unreleased + */ + public function escDonor(Donor $donor): array + { + $donor = $donor->toArray(); + + if ( ! current_user_can('manage_options')) { + $sensitiveProperties = [ + 'userId', + 'email', + 'phone', + 'additionalEmails', + ]; + + foreach ($sensitiveProperties as $property) { + if (array_key_exists($property, $donor)) { + unset($donor[$property]); + } + } + } + + return $donor; + } } diff --git a/tests/Unit/Donations/Routes/GetDonationRouteTest.php b/tests/Unit/Donations/Routes/GetDonationRouteTest.php index 48c3e458fc..eb53240248 100644 --- a/tests/Unit/Donations/Routes/GetDonationRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationRouteTest.php @@ -36,4 +36,33 @@ public function testGetDonation() $this->assertEquals(200, $status); $this->assertEquals($donation->id, $data['id']); } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationShouldNotReturnSensitiveData() + { + /** @var Donation $donation */ + $donation = Donation::factory()->create(); + + $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data, $sensitiveProperties)); + } } diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 6776fc7921..42b83aa7c0 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -51,7 +51,7 @@ public function testGetDonationsWithPagination() $this->assertEquals(200, $status); $this->assertEquals(1, count($data)); - $this->assertEquals($donation1->id, $data[0]->id); + $this->assertEquals($donation1->id, $data[0]['id']); $this->assertEquals(2, $headers['X-WP-Total']); $this->assertEquals(2, $headers['X-WP-TotalPages']); @@ -69,7 +69,7 @@ public function testGetDonationsWithPagination() $this->assertEquals(200, $status); $this->assertEquals(1, count($data)); - $this->assertEquals($donation2->id, $data[0]->id); + $this->assertEquals($donation2->id, $data[0]['id']); $this->assertEquals(2, $headers['X-WP-Total']); $this->assertEquals(2, $headers['X-WP-TotalPages']); } @@ -106,7 +106,35 @@ public function testGetDonationsByCampaignId() $this->assertEquals(200, $status); $this->assertEquals(2, count($data)); - $this->assertEquals($donation1->id, $data[0]->id); - $this->assertEquals($donation2->id, $data[1]->id); + $this->assertEquals($donation1->id, $data[0]['id']); + $this->assertEquals($donation2->id, $data[1]['id']); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsShouldNotReturnSensitiveData() + { + Donation::factory()->create(); + + $route = '/' . DonationRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data[0], $sensitiveProperties)); } } diff --git a/tests/Unit/Donors/Routes/GetDonorRouteTest.php b/tests/Unit/Donors/Routes/GetDonorRouteTest.php index 2843a1b01a..260df55a08 100644 --- a/tests/Unit/Donors/Routes/GetDonorRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorRouteTest.php @@ -33,4 +33,33 @@ public function testGetDonor() $this->assertEquals(200, $status); $this->assertEquals($donor->id, $data['id']); } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorShouldNotReturnSensitiveData() + { + /** @var Donor $donor */ + $donor = Donor::factory()->create(); + + $route = '/' . DonationRoute::NAMESPACE . '/donors/' . $donor->id; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'userId', + 'email', + 'phone', + 'additionalEmails', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data, $sensitiveProperties)); + } } diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 72601b3e58..0ce1841ca5 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -54,7 +54,7 @@ public function testGetDonorsWithPagination() $this->assertEquals(200, $status); $this->assertEquals(1, count($data)); - $this->assertEquals($donor1->id, $data[0]->id); + $this->assertEquals($donor1->id, $data[0]['id']); $this->assertEquals(2, $headers['X-WP-Total']); $this->assertEquals(2, $headers['X-WP-TotalPages']); $request->set_query_params( @@ -71,7 +71,7 @@ public function testGetDonorsWithPagination() $this->assertEquals(200, $status); $this->assertEquals(1, count($data)); - $this->assertEquals($donor2->id, $data[0]->id); + $this->assertEquals($donor2->id, $data[0]['id']); $this->assertEquals(2, $headers['X-WP-Total']); $this->assertEquals(2, $headers['X-WP-TotalPages']); } @@ -114,7 +114,35 @@ public function testGetDonorsByCampaignId() $this->assertEquals(200, $status); $this->assertEquals(2, count($data)); - $this->assertEquals($donor1->id, $data[0]->id); - $this->assertEquals($donor2->id, $data[1]->id); + $this->assertEquals($donor1->id, $data[0]['id']); + $this->assertEquals($donor2->id, $data[1]['id']); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsShouldNotReturnSensitiveData() + { + Donor::factory()->create(); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'userId', + 'email', + 'phone', + 'additionalEmails', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data[0], $sensitiveProperties)); } } From 4dcb2d23e46079c1a45691cac2c4cf429d596eb9 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 10:06:15 -0300 Subject: [PATCH 13/43] refactor: uncomment code --- src/Campaigns/ServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Campaigns/ServiceProvider.php b/src/Campaigns/ServiceProvider.php index 5ab3abf39b..c6c7f6282c 100644 --- a/src/Campaigns/ServiceProvider.php +++ b/src/Campaigns/ServiceProvider.php @@ -142,7 +142,7 @@ private function setupCampaignForms() * @see https://github.com/impress-org/givewp/pull/7483 */ if ( ! defined('GIVE_IS_ALL_STATS_COLUMNS_ASYNC_ON_ADMIN_FORM_LIST_VIEWS')) { - //define('GIVE_IS_ALL_STATS_COLUMNS_ASYNC_ON_ADMIN_FORM_LIST_VIEWS', false); + define('GIVE_IS_ALL_STATS_COLUMNS_ASYNC_ON_ADMIN_FORM_LIST_VIEWS', false); } Hooks::addAction('save_post_give_forms', AddCampaignFormFromRequest::class, 'optionBasedFormEditor', 10, 3); From 7a086f52fdae9c9df1da630d9967e10931ef4d15 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 10:52:19 -0300 Subject: [PATCH 14/43] refactor: add where conditions for donation mode and status --- .../Controllers/DonationRequestController.php | 13 ++++++++++--- .../Unit/Donations/Routes/GetDonationRouteTest.php | 5 +++-- .../Unit/Donations/Routes/GetDonationsRouteTest.php | 11 ++++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 58a00533bd..2006a7326a 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -42,12 +42,19 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response $query = give(DonationRepository::class)->prepareQuery(); if ($campaignId = $request->get_param('campaignId')) { - $query->distinct()->join(function (JoinQueryBuilder $builder) { + // Filter by CampaignId + $query->distinct()->join(function (JoinQueryBuilder $builder) use ($campaignId) { $builder->innerJoin('give_campaign_forms', 'campaign_forms') - ->on('campaign_forms.form_id', "give_donationmeta_attach_meta_formId.meta_value"); - })->where('campaign_forms.campaign_id', $campaignId); + ->joinRaw("ON campaign_forms.form_id = give_donationmeta_attach_meta_formId.meta_value AND campaign_forms.campaign_id = {$campaignId}"); + }); } + // Include only current payment "mode" + $query->where('give_donationmeta_attach_meta_mode.meta_value', give_is_test_mode() ? 'test' : 'live'); + + // Include only valid statuses + $query->whereIn('post_status', ['publish', 'give_subscription']); + $query ->limit($perPage) ->offset(($page - 1) * $perPage); diff --git a/tests/Unit/Donations/Routes/GetDonationRouteTest.php b/tests/Unit/Donations/Routes/GetDonationRouteTest.php index eb53240248..55262d372b 100644 --- a/tests/Unit/Donations/Routes/GetDonationRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationRouteTest.php @@ -5,6 +5,7 @@ use Exception; use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationRoute; +use Give\Donations\ValueObjects\DonationStatus; use Give\Tests\RestApiTestCase; use Give\Tests\TestTraits\RefreshDatabase; use WP_REST_Request; @@ -23,7 +24,7 @@ class GetDonationRouteTest extends RestApiTestCase public function testGetDonation() { /** @var Donation $donation */ - $donation = Donation::factory()->create(); + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -45,7 +46,7 @@ public function testGetDonation() public function testGetDonationShouldNotReturnSensitiveData() { /** @var Donation $donation */ - $donation = Donation::factory()->create(); + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 42b83aa7c0..7d0db10de8 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -7,6 +7,7 @@ use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donations\ValueObjects\DonationRoute; +use Give\Donations\ValueObjects\DonationStatus; use Give\Tests\RestApiTestCase; use Give\Tests\TestTraits\RefreshDatabase; use WP_REST_Request; @@ -29,10 +30,10 @@ public function testGetDonationsWithPagination() Donation::query()->delete(); /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(); + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(); + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -85,11 +86,11 @@ public function testGetDonationsByCampaignId() $campaign = Campaign::factory()->create(); /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(); + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(); + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); $route = '/' . DonationRoute::NAMESPACE . '/donations'; @@ -117,7 +118,7 @@ public function testGetDonationsByCampaignId() */ public function testGetDonationsShouldNotReturnSensitiveData() { - Donation::factory()->create(); + Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); From 4fca5af2d7ecc1665e6f4f951c01efca53652b02 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 10:58:34 -0300 Subject: [PATCH 15/43] refactor: use query method --- src/Donations/Controllers/DonationRequestController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 2006a7326a..f2c9b721d9 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -3,7 +3,6 @@ namespace Give\Donations\Controllers; use Give\Donations\Models\Donation; -use Give\Donations\Repositories\DonationRepository; use Give\Donations\ValueObjects\DonationRoute; use Give\Framework\QueryBuilder\JoinQueryBuilder; use WP_Error; @@ -39,7 +38,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response $page = $request->get_param('page'); $perPage = $request->get_param('per_page'); - $query = give(DonationRepository::class)->prepareQuery(); + $query = Donation::query(); if ($campaignId = $request->get_param('campaignId')) { // Filter by CampaignId From 0aa85a1ffc12f7281734d695bfe28b26e87fa547 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 11:03:18 -0300 Subject: [PATCH 16/43] refactor: remove unnecessary distinct clause --- src/Donations/Controllers/DonationRequestController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index f2c9b721d9..c945241061 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -42,7 +42,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response if ($campaignId = $request->get_param('campaignId')) { // Filter by CampaignId - $query->distinct()->join(function (JoinQueryBuilder $builder) use ($campaignId) { + $query->join(function (JoinQueryBuilder $builder) use ($campaignId) { $builder->innerJoin('give_campaign_forms', 'campaign_forms') ->joinRaw("ON campaign_forms.form_id = give_donationmeta_attach_meta_formId.meta_value AND campaign_forms.campaign_id = {$campaignId}"); }); From 3ada42b77054d82363f10c2459ea87d321135567 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 11:54:13 -0300 Subject: [PATCH 17/43] feature: add hideAnonymousDonations parameter --- .../Controllers/DonationRequestController.php | 5 ++ .../Routes/RegisterDonationRoutes.php | 5 ++ .../Routes/GetDonationsRouteTest.php | 74 +++++++++++++++++-- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index c945241061..f12403b564 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -48,6 +48,11 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response }); } + if ($request->get_param('hideAnonymousDonations')) { + // Remove anonymous donations from results + $query->where('give_donationmeta_attach_meta_anonymous.meta_value', 0); + } + // Include only current payment "mode" $query->where('give_donationmeta_attach_meta_mode.meta_value', give_is_test_mode() ? 'test' : 'live'); diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index d71912a467..eae7399a04 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -97,6 +97,11 @@ public function registerGetDonations() 'required' => false, 'default' => 0, ], + 'hideAnonymousDonations' => [ + 'type' => 'boolean', + 'required' => false, + 'default' => true, + ], ], ] ); diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 7d0db10de8..74f200de7c 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -30,10 +30,10 @@ public function testGetDonationsWithPagination() Donation::query()->delete(); /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -86,11 +86,11 @@ public function testGetDonationsByCampaignId() $campaign = Campaign::factory()->create(); /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); $route = '/' . DonationRoute::NAMESPACE . '/donations'; @@ -118,7 +118,7 @@ public function testGetDonationsByCampaignId() */ public function testGetDonationsShouldNotReturnSensitiveData() { - Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -138,4 +138,68 @@ public function testGetDonationsShouldNotReturnSensitiveData() $this->assertEquals(200, $status); $this->assertEmpty(array_intersect_key($data[0], $sensitiveProperties)); } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsShouldNotReturnAnonymousDonations() + { + Donation::query()->delete(); + + /** @var Donation $donation */ + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + + // This anonymous donation should NOT be returned to the data array. + Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + + $route = '/' . DonationRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(1, count($data)); + $this->assertEquals($donation->id, $data[0]['id']); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsShouldReturnAnonymousDonations() + { + Donation::query()->delete(); + + /** @var Donation $donation1 */ + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + + /** + * This anonymous donation should be returned to the data array. + * @var Donation $donation2 + * */ + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + + $route = '/' . DonationRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'hideAnonymousDonations' => false, + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(2, count($data)); + $this->assertEquals($donation1->id, $data[0]['id']); + $this->assertEquals($donation2->id, $data[1]['id']); + } } From 8ad5a7db50e115a193f3a8eb04494a522d8a6feb Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 12:04:35 -0300 Subject: [PATCH 18/43] refactor: remove unnecessary select --- src/Donors/Controllers/DonorRequestController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index f0b576527f..4f66c04ce9 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -44,8 +44,7 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response $query = give(DonorRepository::class)->prepareQuery(); if ($campaignId = $request->get_param('campaignId')) { - $query->select(['donationmeta1.donation_id', 'donationId']) - ->distinct() + $query->distinct() ->join(function (JoinQueryBuilder $builder) use ($campaignId) { $builder->innerJoin('give_donationmeta', 'donationmeta1') ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); From 205d1a18e47e1dadf26c95acb059b9a10918c79d Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 16:04:00 -0300 Subject: [PATCH 19/43] refactor: improve query readability and consider status and payment mode --- .../Controllers/DonationRequestController.php | 2 +- .../Controllers/DonorRequestController.php | 42 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index f12403b564..68d1634bb3 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -49,7 +49,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response } if ($request->get_param('hideAnonymousDonations')) { - // Remove anonymous donations from results + // Exclude anonymous donations from results $query->where('give_donationmeta_attach_meta_anonymous.meta_value', 0); } diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 4f66c04ce9..8157290a7d 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -4,7 +4,6 @@ use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donors\Models\Donor; -use Give\Donors\Repositories\DonorRepository; use Give\Donors\ValueObjects\DonorRoute; use Give\Framework\QueryBuilder\JoinQueryBuilder; use Give\Framework\QueryBuilder\QueryBuilder; @@ -41,24 +40,37 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response $page = $request->get_param('page'); $perPage = $request->get_param('per_page'); - $query = give(DonorRepository::class)->prepareQuery(); + $query = Donor::query(); + + $query->join(function (JoinQueryBuilder $builder) { + // A donor only can be a donor if it has donations associated with + $builder->innerJoin('give_donationmeta', 'donationmeta1') + ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); + + // Include only current payment "mode" + $mode = give_is_test_mode() ? 'test' : 'live'; + $builder->innerJoin('give_donationmeta', 'donationmeta2') + ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::MODE . "' AND donationmeta2.meta_value = '{$mode}'"); + }); if ($campaignId = $request->get_param('campaignId')) { - $query->distinct() - ->join(function (JoinQueryBuilder $builder) use ($campaignId) { - $builder->innerJoin('give_donationmeta', 'donationmeta1') - ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); - - $builder->innerJoin('give_donationmeta', 'donationmeta2') - ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::CAMPAIGN_ID . "' AND donationmeta2.meta_value = {$campaignId}"); - })->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { - $builder - ->select('ID') - ->from('posts') - ->whereRaw("WHERE ID = donationmeta1.donation_id AND post_type = 'give_payment' AND post_status = 'publish'"); - }); + // Filter by CampaignId + $query->join(function (JoinQueryBuilder $builder) use ($campaignId) { + $builder->innerJoin('give_donationmeta', 'donationmeta3') + ->joinRaw("ON donationmeta3.meta_key = '" . DonationMetaKeys::CAMPAIGN_ID . "' AND donationmeta3.meta_value = {$campaignId}"); + }); } + // Make sure the donation is valid + $query->distinct()->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { + $builder + ->select('ID') + ->from('posts') + ->where('post_type', 'give_payment') + ->whereIn('post_status', ['publish', 'give_subscription']) + ->whereRaw("AND ID = donationmeta1.donation_id"); + }); + $query ->limit($perPage) ->offset(($page - 1) * $perPage); From 811a6a28715b81771cc2383f7c72825dc7ef719c Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 16:44:52 -0300 Subject: [PATCH 20/43] feature: add hideAnonymousDonors parameter --- .../Controllers/DonorRequestController.php | 15 +++- src/Donors/Routes/RegisterDonorRoutes.php | 5 ++ .../Routes/GetDonationsRouteTest.php | 1 + .../Unit/Donors/Routes/GetDonorsRouteTest.php | 83 ++++++++++++++++++- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 8157290a7d..33c546769d 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -50,17 +50,26 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response // Include only current payment "mode" $mode = give_is_test_mode() ? 'test' : 'live'; $builder->innerJoin('give_donationmeta', 'donationmeta2') - ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::MODE . "' AND donationmeta2.meta_value = '{$mode}'"); + ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::MODE . "' AND donationmeta2.meta_value = '{$mode}' AND donationmeta2.donation_id = donationmeta1.donation_id"); }); if ($campaignId = $request->get_param('campaignId')) { // Filter by CampaignId $query->join(function (JoinQueryBuilder $builder) use ($campaignId) { $builder->innerJoin('give_donationmeta', 'donationmeta3') - ->joinRaw("ON donationmeta3.meta_key = '" . DonationMetaKeys::CAMPAIGN_ID . "' AND donationmeta3.meta_value = {$campaignId}"); + ->joinRaw("ON donationmeta3.meta_key = '" . DonationMetaKeys::CAMPAIGN_ID . "' AND donationmeta3.meta_value = {$campaignId} AND donationmeta3.donation_id = donationmeta1.donation_id"); }); } + if ($request->get_param('hideAnonymousDonors')) { + // Exclude anonymous donors from results + $query->distinct() + ->join(function (JoinQueryBuilder $builder) { + $builder->innerJoin('give_donationmeta', 'donationmeta4') + ->joinRaw("ON donationmeta4.meta_key = '" . DonationMetaKeys::ANONYMOUS . "' AND donationmeta4.meta_value = 0 AND donationmeta4.donation_id = donationmeta1.donation_id"); + }); + } + // Make sure the donation is valid $query->distinct()->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { $builder @@ -75,6 +84,8 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response ->limit($perPage) ->offset(($page - 1) * $perPage); + $sql = $query->getSQL(); + $donors = $query->getAll() ?? []; $donors = array_map([$this, 'escDonor'], $donors); $totalDonors = empty($donors) ? 0 : Donor::query()->count(); diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index ee4bc77465..8eaeb340ee 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -97,6 +97,11 @@ public function registerGetDonors() 'required' => false, 'default' => 0, ], + 'hideAnonymousDonors' => [ + 'type' => 'boolean', + 'required' => false, + 'default' => true, + ], ], ] ); diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 74f200de7c..2ddde6037b 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -156,6 +156,7 @@ public function testGetDonationsShouldNotReturnAnonymousDonations() $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $response = $this->dispatchRequest($request); $status = $response->get_status(); diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 0ce1841ca5..c6d04a6f1e 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -89,13 +89,13 @@ public function testGetDonorsByCampaignId() $campaign = Campaign::factory()->create(); /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); $donor1 = $donation1->donor; give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::DONOR_ID, $donor1->id); /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); $donor2 = $donation2->donor; give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::DONOR_ID, $donor2->id); @@ -145,4 +145,83 @@ public function testGetDonorsShouldNotReturnSensitiveData() $this->assertEquals(200, $status); $this->assertEmpty(array_intersect_key($data[0], $sensitiveProperties)); } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsShouldNotReturnAnonymousDonors() + { + Donation::query()->delete(); + + /** @var Campaign $campaign */ + $campaign = Campaign::factory()->create(); + + /** @var Donation $donation */ + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor = $donation->donor; + give()->payment_meta->update_meta($donation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + give()->payment_meta->update_meta($donation->id, DonationMetaKeys::DONOR_ID, $donor->id); + + /** @var Donation $anonymousDonation */ + $anonymousDonation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + $anonymousDonor = $anonymousDonation->donor; // This anonymous donor should NOT be returned to the data array. + give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::DONOR_ID, $anonymousDonor->id); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(1, count($data)); + $this->assertEquals($donor->id, $data[0]['id']); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsShouldReturnAnonymousDonors() + { + Donation::query()->delete(); + + /** @var Campaign $campaign */ + $campaign = Campaign::factory()->create(); + + /** @var Donation $donation */ + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor = $donation->donor; + give()->payment_meta->update_meta($donation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + give()->payment_meta->update_meta($donation->id, DonationMetaKeys::DONOR_ID, $donor->id); + + /** @var Donation $anonymousDonation */ + $anonymousDonation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + $anonymousDonor = $anonymousDonation->donor; // This anonymous donor should be returned to the data array. + give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::DONOR_ID, $anonymousDonor->id); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'hideAnonymousDonors' => false, + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(2, count($data)); + $this->assertEquals($donor->id, $data[0]['id']); + $this->assertEquals($anonymousDonor->id, $data[1]['id']); + } } From 9a8a324378b06fb6b12bfdbaac8f04515e3c8d00 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 16:56:13 -0300 Subject: [PATCH 21/43] fix: broken unit tests --- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index c6d04a6f1e..91f134b506 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -7,7 +7,6 @@ use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donations\ValueObjects\DonationStatus; -use Give\Donors\Models\Donor; use Give\Donors\ValueObjects\DonorRoute; use Give\Framework\Database\DB; use Give\Tests\RestApiTestCase; @@ -31,11 +30,15 @@ public function testGetDonorsWithPagination() { DB::query("DELETE FROM " . DB::prefix('give_donors')); - /** @var Donor $donor1 */ - $donor1 = Donor::factory()->create(); + /** @var Donation $donation1 */ + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor1 = $donation1->donor; + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::DONOR_ID, $donor1->id); - /** @var Donor $donor2 */ - $donor2 = Donor::factory()->create(); + /** @var Donation $donation2 */ + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor2 = $donation2->donor; + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::DONOR_ID, $donor2->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -125,7 +128,10 @@ public function testGetDonorsByCampaignId() */ public function testGetDonorsShouldNotReturnSensitiveData() { - Donor::factory()->create(); + /** @var Donation $donation */ + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor = $donation->donor; + give()->payment_meta->update_meta($donation->id, DonationMetaKeys::DONOR_ID, $donor->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); From 7e83d0d8de34480582f670ed8f60e8abb67b9ed3 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 17:13:35 -0300 Subject: [PATCH 22/43] feature: add onlyWithDonations param --- .../Controllers/DonorRequestController.php | 69 ++++++++++--------- src/Donors/Routes/RegisterDonorRoutes.php | 5 ++ .../Unit/Donors/Routes/GetDonorsRouteTest.php | 26 +++---- 3 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 33c546769d..2386e01bb5 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -42,43 +42,46 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response $query = Donor::query(); - $query->join(function (JoinQueryBuilder $builder) { - // A donor only can be a donor if it has donations associated with - $builder->innerJoin('give_donationmeta', 'donationmeta1') - ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); - - // Include only current payment "mode" - $mode = give_is_test_mode() ? 'test' : 'live'; - $builder->innerJoin('give_donationmeta', 'donationmeta2') - ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::MODE . "' AND donationmeta2.meta_value = '{$mode}' AND donationmeta2.donation_id = donationmeta1.donation_id"); - }); - - if ($campaignId = $request->get_param('campaignId')) { - // Filter by CampaignId - $query->join(function (JoinQueryBuilder $builder) use ($campaignId) { - $builder->innerJoin('give_donationmeta', 'donationmeta3') - ->joinRaw("ON donationmeta3.meta_key = '" . DonationMetaKeys::CAMPAIGN_ID . "' AND donationmeta3.meta_value = {$campaignId} AND donationmeta3.donation_id = donationmeta1.donation_id"); + if ($request->get_param('onlyWithDonations')) { + $query->join(function (JoinQueryBuilder $builder) { + // A donor only can be a donor if it has donations associated with + $builder->innerJoin('give_donationmeta', 'donationmeta1') + ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); + + // Include only current payment "mode" + $mode = give_is_test_mode() ? 'test' : 'live'; + $builder->innerJoin('give_donationmeta', 'donationmeta2') + ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::MODE . "' AND donationmeta2.meta_value = '{$mode}' AND donationmeta2.donation_id = donationmeta1.donation_id"); }); - } - if ($request->get_param('hideAnonymousDonors')) { - // Exclude anonymous donors from results - $query->distinct() - ->join(function (JoinQueryBuilder $builder) { - $builder->innerJoin('give_donationmeta', 'donationmeta4') - ->joinRaw("ON donationmeta4.meta_key = '" . DonationMetaKeys::ANONYMOUS . "' AND donationmeta4.meta_value = 0 AND donationmeta4.donation_id = donationmeta1.donation_id"); + + if ($campaignId = $request->get_param('campaignId')) { + // Filter by CampaignId - Donors only can be filtered by campaignId if they donated to a campaign + $query->join(function (JoinQueryBuilder $builder) use ($campaignId) { + $builder->innerJoin('give_donationmeta', 'donationmeta3') + ->joinRaw("ON donationmeta3.meta_key = '" . DonationMetaKeys::CAMPAIGN_ID . "' AND donationmeta3.meta_value = {$campaignId} AND donationmeta3.donation_id = donationmeta1.donation_id"); }); - } + } - // Make sure the donation is valid - $query->distinct()->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { - $builder - ->select('ID') - ->from('posts') - ->where('post_type', 'give_payment') - ->whereIn('post_status', ['publish', 'give_subscription']) - ->whereRaw("AND ID = donationmeta1.donation_id"); - }); + if ($request->get_param('hideAnonymousDonors')) { + // Exclude anonymous donors from results - Donors only can be excluded if they made an anonymous donation + $query->distinct() + ->join(function (JoinQueryBuilder $builder) { + $builder->innerJoin('give_donationmeta', 'donationmeta4') + ->joinRaw("ON donationmeta4.meta_key = '" . DonationMetaKeys::ANONYMOUS . "' AND donationmeta4.meta_value = 0 AND donationmeta4.donation_id = donationmeta1.donation_id"); + }); + } + + // Make sure the donation is valid + $query->distinct()->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { + $builder + ->select('ID') + ->from('posts') + ->where('post_type', 'give_payment') + ->whereIn('post_status', ['publish', 'give_subscription']) + ->whereRaw("AND ID = donationmeta1.donation_id"); + }); + } $query ->limit($perPage) diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index 8eaeb340ee..013f4ed13a 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -92,6 +92,11 @@ public function registerGetDonors() 'minimum' => 1, 'maximum' => 100, ], + 'onlyWithDonations' => [ + 'type' => 'boolean', + 'required' => false, + 'default' => true, + ], 'campaignId' => [ 'type' => 'integer', 'required' => false, diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 91f134b506..5c1954e0ab 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -7,6 +7,7 @@ use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donations\ValueObjects\DonationStatus; +use Give\Donors\Models\Donor; use Give\Donors\ValueObjects\DonorRoute; use Give\Framework\Database\DB; use Give\Tests\RestApiTestCase; @@ -30,25 +31,23 @@ public function testGetDonorsWithPagination() { DB::query("DELETE FROM " . DB::prefix('give_donors')); - /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - $donor1 = $donation1->donor; - give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::DONOR_ID, $donor1->id); + /** @var Donor $donor1 */ + $donor1 = Donor::factory()->create(); - /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - $donor2 = $donation2->donor; - give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::DONOR_ID, $donor2->id); + /** @var Donor $donor2 */ + $donor2 = Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( [ + 'onlyWithDonations' => false, 'page' => 1, 'per_page' => 1, ] ); + $response = $this->dispatchRequest($request); $status = $response->get_status(); @@ -62,6 +61,7 @@ public function testGetDonorsWithPagination() $this->assertEquals(2, $headers['X-WP-TotalPages']); $request->set_query_params( [ + 'onlyWithDonations' => false, 'page' => 2, 'per_page' => 1, ] @@ -128,13 +128,15 @@ public function testGetDonorsByCampaignId() */ public function testGetDonorsShouldNotReturnSensitiveData() { - /** @var Donation $donation */ - $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - $donor = $donation->donor; - give()->payment_meta->update_meta($donation->id, DonationMetaKeys::DONOR_ID, $donor->id); + Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'onlyWithDonations' => false, + ] + ); $response = $this->dispatchRequest($request); From e57c51e3c35b91030e26f02e9b76c2aa0885c673 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 17:20:41 -0300 Subject: [PATCH 23/43] doc: add comment --- src/Donors/Controllers/DonorRequestController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 2386e01bb5..b0bd733487 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -42,9 +42,10 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response $query = Donor::query(); + // Donors only can be donors if they have donations associated with them if ($request->get_param('onlyWithDonations')) { $query->join(function (JoinQueryBuilder $builder) { - // A donor only can be a donor if it has donations associated with + // The donationmeta1.donation_id should be used in other "donationmeta" joins to make sure we are retrieving data from the proper donation $builder->innerJoin('give_donationmeta', 'donationmeta1') ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); From 4ae26eab83f64597b147d5c798a48fdea015a921 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 17:26:23 -0300 Subject: [PATCH 24/43] refactor: remove unnecessary distinct --- src/Donors/Controllers/DonorRequestController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index b0bd733487..b2761b727c 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -66,8 +66,7 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response if ($request->get_param('hideAnonymousDonors')) { // Exclude anonymous donors from results - Donors only can be excluded if they made an anonymous donation - $query->distinct() - ->join(function (JoinQueryBuilder $builder) { + $query->join(function (JoinQueryBuilder $builder) { $builder->innerJoin('give_donationmeta', 'donationmeta4') ->joinRaw("ON donationmeta4.meta_key = '" . DonationMetaKeys::ANONYMOUS . "' AND donationmeta4.meta_value = 0 AND donationmeta4.donation_id = donationmeta1.donation_id"); }); From c64e98c1fd393853d8460d910aab8103c636fd38 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 19:16:57 -0300 Subject: [PATCH 25/43] refactor: add sort and direction parameters --- .../Controllers/DonationRequestController.php | 5 ++- .../Routes/RegisterDonationRoutes.php | 35 +++++++++++++++++++ .../Controllers/DonorRequestController.php | 5 ++- src/Donors/Routes/RegisterDonorRoutes.php | 30 ++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 68d1634bb3..0aab53be17 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -37,6 +37,8 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response { $page = $request->get_param('page'); $perPage = $request->get_param('per_page'); + $sortColumn = $request->get_param('sort'); + $sortDirection = $request->get_param('direction'); $query = Donation::query(); @@ -61,7 +63,8 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response $query ->limit($perPage) - ->offset(($page - 1) * $perPage); + ->offset(($page - 1) * $perPage) + ->orderBy($sortColumn, $sortDirection); $donations = $query->getAll() ?? []; $donations = array_map([$this, 'escDonation'], $donations); diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index eae7399a04..046b75a0c7 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -12,6 +12,21 @@ */ class RegisterDonationRoutes { + const SORTABLE_COLUMNS = [ + 'id', + 'createdAt', + 'updatedAt', + 'status', + 'amount', + 'feeAmountRecovered', + 'exchangeRate', + 'gatewayId', + 'donorId', + 'honorific', + 'firstName', + 'lastName', + ]; + /** * @var DonationRequestController */ @@ -92,6 +107,26 @@ public function registerGetDonations() 'minimum' => 1, 'maximum' => 100, ], + 'sort' => [ + 'validate_callback' => function ($param) { + if (empty($param)) { + return true; + } + + return in_array($param, self::SORTABLE_COLUMNS, true); + }, + 'default' => 'id', + ], + 'direction' => [ + 'validate_callback' => function ($param) { + if (empty($param)) { + return true; + } + + return in_array(strtoupper($param), ['ASC', 'DESC'], true); + }, + 'default' => 'ASC', + ], 'campaignId' => [ 'type' => 'integer', 'required' => false, diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index b2761b727c..747f53bdba 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -39,6 +39,8 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response { $page = $request->get_param('page'); $perPage = $request->get_param('per_page'); + $sortColumn = $request->get_param('sort'); + $sortDirection = $request->get_param('direction'); $query = Donor::query(); @@ -85,7 +87,8 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response $query ->limit($perPage) - ->offset(($page - 1) * $perPage); + ->offset(($page - 1) * $perPage) + ->orderBy($sortColumn, $sortDirection); $sql = $query->getSQL(); diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index 013f4ed13a..ad15b9ef9a 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -12,6 +12,16 @@ */ class RegisterDonorRoutes { + const SORTABLE_COLUMNS = [ + 'id', + 'createdAt', + 'name', + 'firstName', + 'lastName', + 'totalAmountDonated', + 'totalNumberOfDonations', + ]; + /** * @var DonorRequestController */ @@ -92,6 +102,26 @@ public function registerGetDonors() 'minimum' => 1, 'maximum' => 100, ], + 'sort' => [ + 'validate_callback' => function ($param) { + if (empty($param)) { + return true; + } + + return in_array($param, self::SORTABLE_COLUMNS, true); + }, + 'default' => 'id', + ], + 'direction' => [ + 'validate_callback' => function ($param) { + if (empty($param)) { + return true; + } + + return in_array(strtoupper($param), ['ASC', 'DESC'], true); + }, + 'default' => 'ASC', + ], 'onlyWithDonations' => [ 'type' => 'boolean', 'required' => false, From 7aa99037d42dcb7adf8fc850d344edd5b91c2b39 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Thu, 30 Jan 2025 22:37:23 -0300 Subject: [PATCH 26/43] tests: add testGetDonorsSortedByTotalAmountDonated method --- .../Controllers/DonationRequestController.php | 25 ++- .../Controllers/DonorRequestController.php | 20 +- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 177 ++++++++++++++++++ 3 files changed, 220 insertions(+), 2 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 0aab53be17..c7801e1757 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -37,7 +37,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response { $page = $request->get_param('page'); $perPage = $request->get_param('per_page'); - $sortColumn = $request->get_param('sort'); + $sortColumn = $this->getSortColumn($request->get_param('sort')); $sortDirection = $request->get_param('direction'); $query = Donation::query(); @@ -128,4 +128,27 @@ public function escDonation(Donation $donation): array return $donation; } + + /** + * @unreleased + */ + public function getSortColumn(string $sortColumn): string + { + $sortColumnsMap = [ + 'id' => 'ID', + 'createdAt' => 'post_date', + 'updatedAt' => 'post_modified', + 'status' => 'post_status', + 'amount' => 'give_donationmeta_attach_meta_amount.meta_value', + 'feeAmountRecovered' => 'give_donationmeta_attach_meta_feeAmountRecovered.meta_value', + 'exchangeRate' => 'give_donationmeta_attach_meta_exchangeRate.meta_value', + 'gatewayId' => 'give_donationmeta_attach_meta_gateway.meta_value', + 'donorId' => 'give_donationmeta_attach_meta_donorId.meta_value', + 'honorific' => 'give_donationmeta_attach_meta_honorific.meta_value', + 'firstName' => 'give_donationmeta_attach_meta_firstName.meta_value', + 'lastName' => 'give_donationmeta_attach_meta_lastName.meta_value', + ]; + + return $sortColumnsMap[$sortColumn]; + } } diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 747f53bdba..d2d4b3c6e0 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -39,7 +39,7 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response { $page = $request->get_param('page'); $perPage = $request->get_param('per_page'); - $sortColumn = $request->get_param('sort'); + $sortColumn = $this->getSortColumn($request->get_param('sort')); $sortDirection = $request->get_param('direction'); $query = Donor::query(); @@ -154,4 +154,22 @@ public function escDonor(Donor $donor): array return $donor; } + + /** + * @unreleased + */ + public function getSortColumn(string $sortColumn): string + { + $sortColumnsMap = [ + 'id' => 'id', + 'createdAt' => 'date_created', + 'name' => 'name', + 'firstName' => 'give_donormeta_attach_meta_firstName.meta_value', + 'lastName' => 'give_donormeta_attach_meta_lastName.meta_value', + 'totalAmountDonated' => 'purchase_value', + 'totalNumberOfDonations' => 'purchase_count', + ]; + + return $sortColumnsMap[$sortColumn]; + } } diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 5c1954e0ab..cff813134d 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -10,6 +10,7 @@ use Give\Donors\Models\Donor; use Give\Donors\ValueObjects\DonorRoute; use Give\Framework\Database\DB; +use Give\Framework\Support\ValueObjects\Money; use Give\Tests\RestApiTestCase; use Give\Tests\TestTraits\RefreshDatabase; use WP_REST_Request; @@ -232,4 +233,180 @@ public function testGetDonorsShouldReturnAnonymousDonors() $this->assertEquals($donor->id, $data[0]['id']); $this->assertEquals($anonymousDonor->id, $data[1]['id']); } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsSortedByTotalAmountDonated() + { + DB::query("DELETE FROM " . DB::prefix('give_donors')); + + /** @var Campaign $campaign1 */ + $campaign1 = Campaign::factory()->create(); + + /** @var Campaign $campaign2 */ + $campaign2 = Campaign::factory()->create(); + + + $donor1 = $this->getDonor1ForSortTest($campaign1->id); + $donor2 = $this->getDonor2ForSortTest($campaign1->id); + $donor3 = $this->getDonor3ForSortTest($campaign2->id); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + /** + * Ascendant Direction + */ + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 30, + 'sort' => 'totalAmountDonated', + 'direction' => 'ASC', + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(3, count($data)); + $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor3->totalAmountDonated->getAmount(), $data[2]['totalAmountDonated']->getAmount()); + + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 30, + 'sort' => 'totalAmountDonated', + 'direction' => 'ASC', + 'campaignId' => $campaign1->id, // Filtering by campaignId + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(2, count($data)); + $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); + + /** + * Descendant Direction + */ + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 3, + 'sort' => 'totalAmountDonated', + 'direction' => 'DESC', + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(3, count($data)); + $this->assertEquals($donor3->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[2]['totalAmountDonated']->getAmount()); + + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 30, + 'sort' => 'totalAmountDonated', + 'direction' => 'DESC', + 'campaignId' => $campaign1->id, // Filtering by campaignId + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(2, count($data)); + $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); + } + + /** + * @throws Exception + */ + private function getDonor1ForSortTest($campaignId): Donor + { + /** @var Donation $donation1 */ + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor1 = $donation1->donor; + + $donor1->firstName = 'A'; + $donor1->lastName = 'A'; + $donor1->name = 'A A'; + $donor1->totalAmountDonated = new Money(100, 'USD'); + $donor1->totalNumberOfDonations = 1; + $donor1->save(); + + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::DONOR_ID, $donor1->id); + + return Donor::find($donor1->id); + } + + /** + * @throws Exception + */ + private function getDonor2ForSortTest($campaignId): Donor + { + /** @var Donation $donation2 */ + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor2 = $donation2->donor; + + $donor2->firstName = 'B'; + $donor2->lastName = 'B'; + $donor2->name = 'B B'; + $donor2->totalAmountDonated = new Money(200, 'USD'); + $donor2->totalNumberOfDonations = 2; + $donor2->save(); + + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::DONOR_ID, $donor2->id); + + return Donor::find($donor2->id); + } + + /** + * @throws Exception + */ + private function getDonor3ForSortTest($campaignId): Donor + { + /** @var Donation $donation3 */ + $donation3 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donor3 = $donation3->donor; + + $donor3->firstName = 'C'; + $donor3->lastName = 'C'; + $donor3->name = 'C C'; + $donor3->totalAmountDonated = new Money(300, 'USD'); + $donor3->totalNumberOfDonations = 3; + $donor3->save(); + + give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::DONOR_ID, $donor3->id); + + return Donor::find($donor3->id); + } } From 7fee06047cce2fb4a40d2e27deeb079485da815d Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 09:48:35 -0300 Subject: [PATCH 27/43] tests: add sortableColumnsDataProvider and refactor test to use it --- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index cff813134d..b0c159900a 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -237,9 +237,11 @@ public function testGetDonorsShouldReturnAnonymousDonors() /** * @unreleased * + * @dataProvider sortableColumnsDataProvider + * * @throws Exception */ - public function testGetDonorsSortedByTotalAmountDonated() + public function testGetDonorsSortedColumns($sortableColumn) { DB::query("DELETE FROM " . DB::prefix('give_donors')); @@ -250,9 +252,9 @@ public function testGetDonorsSortedByTotalAmountDonated() $campaign2 = Campaign::factory()->create(); - $donor1 = $this->getDonor1ForSortTest($campaign1->id); - $donor2 = $this->getDonor2ForSortTest($campaign1->id); - $donor3 = $this->getDonor3ForSortTest($campaign2->id); + $donor1 = $this->getDonor1WithDonationAssociated($campaign1->id); + $donor2 = $this->getDonor2WithDonationAssociated($campaign1->id); + $donor3 = $this->getDonor3WithDonationAssociated($campaign2->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -264,7 +266,7 @@ public function testGetDonorsSortedByTotalAmountDonated() [ 'page' => 1, 'per_page' => 30, - 'sort' => 'totalAmountDonated', + 'sort' => $sortableColumn, 'direction' => 'ASC', ] ); @@ -276,15 +278,15 @@ public function testGetDonorsSortedByTotalAmountDonated() $this->assertEquals(200, $status); $this->assertEquals(3, count($data)); - $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); - $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); - $this->assertEquals($donor3->totalAmountDonated->getAmount(), $data[2]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor1->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donor2->{$sortableColumn}, $data[1][$sortableColumn]); + $this->assertEquals($donor3->{$sortableColumn}, $data[2][$sortableColumn]); $request->set_query_params( [ 'page' => 1, 'per_page' => 30, - 'sort' => 'totalAmountDonated', + 'sort' => $sortableColumn, 'direction' => 'ASC', 'campaignId' => $campaign1->id, // Filtering by campaignId ] @@ -297,8 +299,8 @@ public function testGetDonorsSortedByTotalAmountDonated() $this->assertEquals(200, $status); $this->assertEquals(2, count($data)); - $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); - $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor1->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donor2->{$sortableColumn}, $data[1][$sortableColumn]); /** * Descendant Direction @@ -307,7 +309,7 @@ public function testGetDonorsSortedByTotalAmountDonated() [ 'page' => 1, 'per_page' => 3, - 'sort' => 'totalAmountDonated', + 'sort' => $sortableColumn, 'direction' => 'DESC', ] ); @@ -319,15 +321,15 @@ public function testGetDonorsSortedByTotalAmountDonated() $this->assertEquals(200, $status); $this->assertEquals(3, count($data)); - $this->assertEquals($donor3->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); - $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); - $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[2]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor3->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donor2->{$sortableColumn}, $data[1][$sortableColumn]); + $this->assertEquals($donor1->{$sortableColumn}, $data[2][$sortableColumn]); $request->set_query_params( [ 'page' => 1, 'per_page' => 30, - 'sort' => 'totalAmountDonated', + 'sort' => $sortableColumn, 'direction' => 'DESC', 'campaignId' => $campaign1->id, // Filtering by campaignId ] @@ -340,17 +342,30 @@ public function testGetDonorsSortedByTotalAmountDonated() $this->assertEquals(200, $status); $this->assertEquals(2, count($data)); - $this->assertEquals($donor2->totalAmountDonated->getAmount(), $data[0]['totalAmountDonated']->getAmount()); - $this->assertEquals($donor1->totalAmountDonated->getAmount(), $data[1]['totalAmountDonated']->getAmount()); + $this->assertEquals($donor2->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donor1->{$sortableColumn}, $data[1][$sortableColumn]); + } + + public function sortableColumnsDataProvider(): array + { + return [ + ['id'], + ['createdAt'], + ['name'], + ['firstName'], + ['lastName'], + ['totalAmountDonated'], + ['totalNumberOfDonations'], + ]; } /** * @throws Exception */ - private function getDonor1ForSortTest($campaignId): Donor + private function getDonor1WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); $donor1 = $donation1->donor; $donor1->firstName = 'A'; @@ -369,10 +384,10 @@ private function getDonor1ForSortTest($campaignId): Donor /** * @throws Exception */ - private function getDonor2ForSortTest($campaignId): Donor + private function getDonor2WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); $donor2 = $donation2->donor; $donor2->firstName = 'B'; @@ -391,10 +406,10 @@ private function getDonor2ForSortTest($campaignId): Donor /** * @throws Exception */ - private function getDonor3ForSortTest($campaignId): Donor + private function getDonor3WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation3 */ - $donation3 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donation3 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); $donor3 = $donation3->donor; $donor3->firstName = 'C'; From e0c0cc341be7bb8fea9fe046fadf55e317c2a20d Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 09:56:58 -0300 Subject: [PATCH 28/43] tests: simplify logic --- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 45 ++++--------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index b0c159900a..741e36331e 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -92,17 +92,8 @@ public function testGetDonorsByCampaignId() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - $donor1 = $donation1->donor; - give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::DONOR_ID, $donor1->id); - - /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - $donor2 = $donation2->donor; - give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::DONOR_ID, $donor2->id); + $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); + $donor2 = $this->getDonor2WithDonationAssociated($campaign->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -167,17 +158,8 @@ public function testGetDonorsShouldNotReturnAnonymousDonors() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - /** @var Donation $donation */ - $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - $donor = $donation->donor; - give()->payment_meta->update_meta($donation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - give()->payment_meta->update_meta($donation->id, DonationMetaKeys::DONOR_ID, $donor->id); - - /** @var Donation $anonymousDonation */ - $anonymousDonation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); - $anonymousDonor = $anonymousDonation->donor; // This anonymous donor should NOT be returned to the data array. - give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::DONOR_ID, $anonymousDonor->id); + $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); + $donor2 = $this->getDonor2WithDonationAssociated($campaign->id, true); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -188,7 +170,7 @@ public function testGetDonorsShouldNotReturnAnonymousDonors() $this->assertEquals(200, $status); $this->assertEquals(1, count($data)); - $this->assertEquals($donor->id, $data[0]['id']); + $this->assertEquals($donor1->id, $data[0]['id']); } /** @@ -203,17 +185,8 @@ public function testGetDonorsShouldReturnAnonymousDonors() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - /** @var Donation $donation */ - $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - $donor = $donation->donor; - give()->payment_meta->update_meta($donation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - give()->payment_meta->update_meta($donation->id, DonationMetaKeys::DONOR_ID, $donor->id); - - /** @var Donation $anonymousDonation */ - $anonymousDonation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); - $anonymousDonor = $anonymousDonation->donor; // This anonymous donor should be returned to the data array. - give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - give()->payment_meta->update_meta($anonymousDonation->id, DonationMetaKeys::DONOR_ID, $anonymousDonor->id); + $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); + $donor2 = $this->getDonor2WithDonationAssociated($campaign->id, true); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -230,8 +203,8 @@ public function testGetDonorsShouldReturnAnonymousDonors() $this->assertEquals(200, $status); $this->assertEquals(2, count($data)); - $this->assertEquals($donor->id, $data[0]['id']); - $this->assertEquals($anonymousDonor->id, $data[1]['id']); + $this->assertEquals($donor1->id, $data[0]['id']); + $this->assertEquals($donor2->id, $data[1]['id']); } /** From b54e0b40ca20767c168e6cef852668a7c13640e0 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 10:11:53 -0300 Subject: [PATCH 29/43] tests: add testGetDonorsShouldReturnSensitiveData method --- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 741e36331e..8a228b2d56 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -143,7 +143,50 @@ public function testGetDonorsShouldNotReturnSensitiveData() ]; $this->assertEquals(200, $status); - $this->assertEmpty(array_intersect_key($data[0], $sensitiveProperties)); + $this->assertEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsShouldReturnSensitiveData() + { + $newAdminUser = $this->factory()->user->create( + [ + 'role' => 'administrator', + 'user_login' => 'admin38974238473824', + 'user_pass' => 'admin38974238473824', + 'user_email' => 'admin38974238473824@test.com', + ] + ); + wp_set_current_user($newAdminUser); + + Donor::factory()->create(); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'onlyWithDonations' => false, + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'userId', + 'email', + 'phone', + 'additionalEmails', + ]; + + $this->assertEquals(200, $status); + $this->assertNotEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); } /** From 783655b25f8dc67bdbf1b4b4109fb3c97f2dddf0 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 10:21:15 -0300 Subject: [PATCH 30/43] tests: add new tests for the onlyWithDonations param --- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 177 ++++++++++++------ 1 file changed, 122 insertions(+), 55 deletions(-) diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 8a228b2d56..cee3939c83 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -28,24 +28,58 @@ class GetDonorsRouteTest extends RestApiTestCase * * @throws Exception */ - public function testGetDonorsWithPagination() + public function testGetDonorsShouldNotReturnSensitiveData() { - DB::query("DELETE FROM " . DB::prefix('give_donors')); + Donor::factory()->create(); - /** @var Donor $donor1 */ - $donor1 = Donor::factory()->create(); + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'onlyWithDonations' => false, + ] + ); - /** @var Donor $donor2 */ - $donor2 = Donor::factory()->create(); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'userId', + 'email', + 'phone', + 'additionalEmails', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsShouldReturnSensitiveData() + { + $newAdminUser = $this->factory()->user->create( + [ + 'role' => 'administrator', + 'user_login' => 'admin38974238473824', + 'user_pass' => 'admin38974238473824', + 'user_email' => 'admin38974238473824@test.com', + ] + ); + wp_set_current_user($newAdminUser); + + Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); - $request->set_query_params( [ 'onlyWithDonations' => false, - 'page' => 1, - 'per_page' => 1, ] ); @@ -53,31 +87,49 @@ public function testGetDonorsWithPagination() $status = $response->get_status(); $data = $response->get_data(); - $headers = $response->get_headers(); + + $sensitiveProperties = [ + 'userId', + 'email', + 'phone', + 'additionalEmails', + ]; $this->assertEquals(200, $status); - $this->assertEquals(1, count($data)); - $this->assertEquals($donor1->id, $data[0]['id']); - $this->assertEquals(2, $headers['X-WP-Total']); - $this->assertEquals(2, $headers['X-WP-TotalPages']); + $this->assertNotEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonorsShouldReturnOnlyDonorsWithDonations() + { + DB::query("DELETE FROM " . DB::prefix('give_donors')); + + /** @var Campaign $campaign */ + $campaign = Campaign::factory()->create(); + + $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); + $donor2 = Donor::factory()->create(); + + $route = '/' . DonorRoute::NAMESPACE . '/donors'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( [ - 'onlyWithDonations' => false, - 'page' => 2, - 'per_page' => 1, + 'onlyWithDonations' => true, ] ); + $response = $this->dispatchRequest($request); $status = $response->get_status(); $data = $response->get_data(); - $headers = $response->get_headers(); $this->assertEquals(200, $status); $this->assertEquals(1, count($data)); - $this->assertEquals($donor2->id, $data[0]['id']); - $this->assertEquals(2, $headers['X-WP-Total']); - $this->assertEquals(2, $headers['X-WP-TotalPages']); + $this->assertEquals($donor1->id, $data[0]['id']); } /** @@ -85,23 +137,24 @@ public function testGetDonorsWithPagination() * * @throws Exception */ - public function testGetDonorsByCampaignId() + public function testGetDonorsShouldReturnDonorsWithOrWithoutDonations() { - Donation::query()->delete(); + DB::query("DELETE FROM " . DB::prefix('give_donors')); /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); - $donor2 = $this->getDonor2WithDonationAssociated($campaign->id); + $donor2 = Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( [ - 'campaignId' => $campaign->id, + 'onlyWithDonations' => false, ] ); + $response = $this->dispatchRequest($request); $status = $response->get_status(); @@ -118,15 +171,24 @@ public function testGetDonorsByCampaignId() * * @throws Exception */ - public function testGetDonorsShouldNotReturnSensitiveData() + public function testGetDonorsWithPagination() { - Donor::factory()->create(); + DB::query("DELETE FROM " . DB::prefix('give_donors')); + + /** @var Donor $donor1 */ + $donor1 = Donor::factory()->create(); + + /** @var Donor $donor2 */ + $donor2 = Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( [ 'onlyWithDonations' => false, + 'page' => 1, + 'per_page' => 1, ] ); @@ -134,16 +196,31 @@ public function testGetDonorsShouldNotReturnSensitiveData() $status = $response->get_status(); $data = $response->get_data(); + $headers = $response->get_headers(); - $sensitiveProperties = [ - 'userId', - 'email', - 'phone', - 'additionalEmails', - ]; + $this->assertEquals(200, $status); + $this->assertEquals(1, count($data)); + $this->assertEquals($donor1->id, $data[0]['id']); + $this->assertEquals(2, $headers['X-WP-Total']); + $this->assertEquals(2, $headers['X-WP-TotalPages']); + $request->set_query_params( + [ + 'onlyWithDonations' => false, + 'page' => 2, + 'per_page' => 1, + ] + ); + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + $headers = $response->get_headers(); $this->assertEquals(200, $status); - $this->assertEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + $this->assertEquals(1, count($data)); + $this->assertEquals($donor2->id, $data[0]['id']); + $this->assertEquals(2, $headers['X-WP-Total']); + $this->assertEquals(2, $headers['X-WP-TotalPages']); } /** @@ -151,42 +228,32 @@ public function testGetDonorsShouldNotReturnSensitiveData() * * @throws Exception */ - public function testGetDonorsShouldReturnSensitiveData() + public function testGetDonorsByCampaignId() { - $newAdminUser = $this->factory()->user->create( - [ - 'role' => 'administrator', - 'user_login' => 'admin38974238473824', - 'user_pass' => 'admin38974238473824', - 'user_email' => 'admin38974238473824@test.com', - ] - ); - wp_set_current_user($newAdminUser); + Donation::query()->delete(); - Donor::factory()->create(); + /** @var Campaign $campaign */ + $campaign = Campaign::factory()->create(); + + $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); + $donor2 = $this->getDonor2WithDonationAssociated($campaign->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( [ - 'onlyWithDonations' => false, + 'campaignId' => $campaign->id, ] ); - $response = $this->dispatchRequest($request); $status = $response->get_status(); $data = $response->get_data(); - $sensitiveProperties = [ - 'userId', - 'email', - 'phone', - 'additionalEmails', - ]; - $this->assertEquals(200, $status); - $this->assertNotEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + $this->assertEquals(2, count($data)); + $this->assertEquals($donor1->id, $data[0]['id']); + $this->assertEquals($donor2->id, $data[1]['id']); } /** From e3eb16783508f0f922c645fb1a5b61fd58c08356 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 11:08:57 -0300 Subject: [PATCH 31/43] tests: add testGetDonationsShouldReturnSensitiveData method --- .../Routes/GetDonationsRouteTest.php | 96 +++++++++++++------ 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 2ddde6037b..94a48c6b59 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -20,6 +20,72 @@ class GetDonationsRouteTest extends RestApiTestCase { use RefreshDatabase; + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsShouldNotReturnSensitiveData() + { + Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + + $route = '/' . DonationRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsShouldReturnSensitiveData() + { + $newAdminUser = $this->factory()->user->create( + [ + 'role' => 'administrator', + 'user_login' => 'admin38974238473824', + 'user_pass' => 'admin38974238473824', + 'user_email' => 'admin38974238473824@test.com', + ] + ); + wp_set_current_user($newAdminUser); + + Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + + $route = '/' . DonationRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveProperties = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; + + $this->assertEquals(200, $status); + $this->assertNotEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + } + /** * @unreleased * @@ -111,34 +177,6 @@ public function testGetDonationsByCampaignId() $this->assertEquals($donation2->id, $data[1]['id']); } - /** - * @unreleased - * - * @throws Exception - */ - public function testGetDonationsShouldNotReturnSensitiveData() - { - Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - - $route = '/' . DonationRoute::NAMESPACE . '/donations'; - $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); - - $response = $this->dispatchRequest($request); - - $status = $response->get_status(); - $data = $response->get_data(); - - $sensitiveProperties = [ - 'donorIp', - 'email', - 'phone', - 'billingAddress', - ]; - - $this->assertEquals(200, $status); - $this->assertEmpty(array_intersect_key($data[0], $sensitiveProperties)); - } - /** * @unreleased * @@ -156,7 +194,7 @@ public function testGetDonationsShouldNotReturnAnonymousDonations() $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); - + $response = $this->dispatchRequest($request); $status = $response->get_status(); From bf7214a5eb8ea3c648cac8c0ff51e197f09d73bb Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 11:36:49 -0300 Subject: [PATCH 32/43] refactor: simplify query --- .../Controllers/DonationRequestController.php | 11 +++-------- src/Donations/Routes/RegisterDonationRoutes.php | 3 --- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index c7801e1757..38a5a317a7 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -4,7 +4,6 @@ use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationRoute; -use Give\Framework\QueryBuilder\JoinQueryBuilder; use WP_Error; use WP_REST_Request; use WP_REST_Response; @@ -44,10 +43,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response if ($campaignId = $request->get_param('campaignId')) { // Filter by CampaignId - $query->join(function (JoinQueryBuilder $builder) use ($campaignId) { - $builder->innerJoin('give_campaign_forms', 'campaign_forms') - ->joinRaw("ON campaign_forms.form_id = give_donationmeta_attach_meta_formId.meta_value AND campaign_forms.campaign_id = {$campaignId}"); - }); + $query->where('give_donationmeta_attach_meta_campaignId.meta_value', $campaignId); } if ($request->get_param('hideAnonymousDonations')) { @@ -66,6 +62,8 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response ->offset(($page - 1) * $perPage) ->orderBy($sortColumn, $sortDirection); + $sql = $query->getSQL(); + $donations = $query->getAll() ?? []; $donations = array_map([$this, 'escDonation'], $donations); $totalDonations = empty($donations) ? 0 : Donation::query()->count(); @@ -141,10 +139,7 @@ public function getSortColumn(string $sortColumn): string 'status' => 'post_status', 'amount' => 'give_donationmeta_attach_meta_amount.meta_value', 'feeAmountRecovered' => 'give_donationmeta_attach_meta_feeAmountRecovered.meta_value', - 'exchangeRate' => 'give_donationmeta_attach_meta_exchangeRate.meta_value', - 'gatewayId' => 'give_donationmeta_attach_meta_gateway.meta_value', 'donorId' => 'give_donationmeta_attach_meta_donorId.meta_value', - 'honorific' => 'give_donationmeta_attach_meta_honorific.meta_value', 'firstName' => 'give_donationmeta_attach_meta_firstName.meta_value', 'lastName' => 'give_donationmeta_attach_meta_lastName.meta_value', ]; diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index 046b75a0c7..1dd5439c56 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -19,10 +19,7 @@ class RegisterDonationRoutes 'status', 'amount', 'feeAmountRecovered', - 'exchangeRate', - 'gatewayId', 'donorId', - 'honorific', 'firstName', 'lastName', ]; From 563a7260d163cb18a1a9d46272abaab218669920 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 11:37:04 -0300 Subject: [PATCH 33/43] tests: add testGetDonationsSortedByColumns --- .../Routes/GetDonationsRouteTest.php | 195 ++++++++++++++++++ .../Unit/Donors/Routes/GetDonorsRouteTest.php | 11 +- 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 94a48c6b59..09228aeace 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -8,6 +8,8 @@ use Give\Donations\ValueObjects\DonationMetaKeys; use Give\Donations\ValueObjects\DonationRoute; use Give\Donations\ValueObjects\DonationStatus; +use Give\Donors\ValueObjects\DonorRoute; +use Give\Framework\Support\ValueObjects\Money; use Give\Tests\RestApiTestCase; use Give\Tests\TestTraits\RefreshDatabase; use WP_REST_Request; @@ -241,4 +243,197 @@ public function testGetDonationsShouldReturnAnonymousDonations() $this->assertEquals($donation1->id, $data[0]['id']); $this->assertEquals($donation2->id, $data[1]['id']); } + + /** + * @unreleased + * + * @dataProvider sortableColumnsDataProvider + * + * @throws Exception + */ + public function testGetDonationsSortedByColumns($sortableColumn) + { + Donation::query()->delete(); + + /** @var Campaign $campaign1 */ + $campaign1 = Campaign::factory()->create(); + + /** @var Campaign $campaign2 */ + $campaign2 = Campaign::factory()->create(); + + + $donation1 = $this->getDonation1($campaign1->id); + $donation2 = $this->getDonation2($campaign1->id); + $donation3 = $this->getDonation3($campaign2->id); + + $route = '/' . DonorRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + /** + * Ascendant Direction + */ + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 30, + 'sort' => $sortableColumn, + 'direction' => 'ASC', + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(3, count($data)); + $this->assertEquals($donation1->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donation2->{$sortableColumn}, $data[1][$sortableColumn]); + $this->assertEquals($donation3->{$sortableColumn}, $data[2][$sortableColumn]); + + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 30, + 'sort' => $sortableColumn, + 'direction' => 'ASC', + 'campaignId' => $campaign1->id, // Filtering by campaignId + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(2, count($data)); + $this->assertEquals($donation1->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donation2->{$sortableColumn}, $data[1][$sortableColumn]); + + /** + * Descendant Direction + */ + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 3, + 'sort' => $sortableColumn, + 'direction' => 'DESC', + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(3, count($data)); + $this->assertEquals($donation3->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donation2->{$sortableColumn}, $data[1][$sortableColumn]); + $this->assertEquals($donation1->{$sortableColumn}, $data[2][$sortableColumn]); + + $request->set_query_params( + [ + 'page' => 1, + 'per_page' => 30, + 'sort' => $sortableColumn, + 'direction' => 'DESC', + 'campaignId' => $campaign1->id, // Filtering by campaignId + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $this->assertEquals(200, $status); + $this->assertEquals(2, count($data)); + $this->assertEquals($donation2->{$sortableColumn}, $data[0][$sortableColumn]); + $this->assertEquals($donation1->{$sortableColumn}, $data[1][$sortableColumn]); + } + + /** + * @unreleased + */ + public function sortableColumnsDataProvider(): array + { + return [ + ['id'], + ['createdAt'], + ['updatedAt'], + ['status'], + ['amount'], + ['feeAmountRecovered'], + ['donorId'], + ['firstName'], + ['lastName'], + ]; + } + + /** + * @unreleased + * + * @throws Exception + */ + private function getDonation1(int $campaignId, bool $anonymous = false): Donation + { + /** @var Donation $donation1 */ + $donation1 = Donation::factory()->create([ + 'status' => DonationStatus::COMPLETE(), + 'anonymous' => $anonymous, + 'amount' => new Money(100, 'USD'), + 'feeAmountRecovered' => new Money(10, 'USD'), + 'firstName' => 'A', + 'lastName' => 'A', + ]); + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + + return $donation1; + } + + /** + * @unreleased + * + * @throws Exception + */ + private function getDonation2(int $campaignId, bool $anonymous = false): Donation + { + /** @var Donation $donation2 */ + $donation2 = Donation::factory()->create([ + 'status' => DonationStatus::COMPLETE(), + 'anonymous' => $anonymous, + 'amount' => new Money(200, 'USD'), + 'feeAmountRecovered' => new Money(20, 'USD'), + 'firstName' => 'B', + 'lastName' => 'B', + ]); + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + + return $donation2; + } + + /** + * @unreleased + * + * @throws Exception + */ + private function getDonation3(int $campaignId, bool $anonymous = false): Donation + { + /** @var Donation $donation3 */ + $donation3 = Donation::factory()->create([ + 'status' => DonationStatus::COMPLETE(), + 'anonymous' => $anonymous, + 'amount' => new Money(300, 'USD'), + 'feeAmountRecovered' => new Money(30, 'USD'), + 'firstName' => 'C', + 'lastName' => 'C', + ]); + give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + + return $donation3; + } } diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index cee3939c83..f4ef285d3f 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -324,7 +324,7 @@ public function testGetDonorsShouldReturnAnonymousDonors() * * @throws Exception */ - public function testGetDonorsSortedColumns($sortableColumn) + public function testGetDonorsSortedByColumns($sortableColumn) { DB::query("DELETE FROM " . DB::prefix('give_donors')); @@ -429,6 +429,9 @@ public function testGetDonorsSortedColumns($sortableColumn) $this->assertEquals($donor1->{$sortableColumn}, $data[1][$sortableColumn]); } + /** + * @unreleased + */ public function sortableColumnsDataProvider(): array { return [ @@ -443,6 +446,8 @@ public function sortableColumnsDataProvider(): array } /** + * @unreleased + * * @throws Exception */ private function getDonor1WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor @@ -465,6 +470,8 @@ private function getDonor1WithDonationAssociated(int $campaignId, bool $anonymou } /** + * @unreleased + * * @throws Exception */ private function getDonor2WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor @@ -487,6 +494,8 @@ private function getDonor2WithDonationAssociated(int $campaignId, bool $anonymou } /** + * @unreleased + * * @throws Exception */ private function getDonor3WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor From ca5c7a716d0bd7f7b3179c5d4b69985dbbda3170 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 11:56:40 -0300 Subject: [PATCH 34/43] refactor: replace validate_callback with enum --- .../Controllers/DonationRequestController.php | 2 - .../Routes/RegisterDonationRoutes.php | 42 +++++++------------ .../Controllers/DonorRequestController.php | 2 - src/Donors/Routes/RegisterDonorRoutes.php | 39 ++++++----------- 4 files changed, 26 insertions(+), 59 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 38a5a317a7..4584e57d26 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -62,8 +62,6 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response ->offset(($page - 1) * $perPage) ->orderBy($sortColumn, $sortDirection); - $sql = $query->getSQL(); - $donations = $query->getAll() ?? []; $donations = array_map([$this, 'escDonation'], $donations); $totalDonations = empty($donations) ? 0 : Donation::query()->count(); diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index 1dd5439c56..cd60272be3 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -12,18 +12,6 @@ */ class RegisterDonationRoutes { - const SORTABLE_COLUMNS = [ - 'id', - 'createdAt', - 'updatedAt', - 'status', - 'amount', - 'feeAmountRecovered', - 'donorId', - 'firstName', - 'lastName', - ]; - /** * @var DonationRequestController */ @@ -105,33 +93,31 @@ public function registerGetDonations() 'maximum' => 100, ], 'sort' => [ - 'validate_callback' => function ($param) { - if (empty($param)) { - return true; - } - - return in_array($param, self::SORTABLE_COLUMNS, true); - }, + 'type' => 'string', 'default' => 'id', + 'enum' => [ + 'id', + 'createdAt', + 'updatedAt', + 'status', + 'amount', + 'feeAmountRecovered', + 'donorId', + 'firstName', + 'lastName', + ], ], 'direction' => [ - 'validate_callback' => function ($param) { - if (empty($param)) { - return true; - } - - return in_array(strtoupper($param), ['ASC', 'DESC'], true); - }, + 'type' => 'string', 'default' => 'ASC', + 'enum' => ['ASC', 'DESC'], ], 'campaignId' => [ 'type' => 'integer', - 'required' => false, 'default' => 0, ], 'hideAnonymousDonations' => [ 'type' => 'boolean', - 'required' => false, 'default' => true, ], ], diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index d2d4b3c6e0..bb08dcf3fe 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -90,8 +90,6 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response ->offset(($page - 1) * $perPage) ->orderBy($sortColumn, $sortDirection); - $sql = $query->getSQL(); - $donors = $query->getAll() ?? []; $donors = array_map([$this, 'escDonor'], $donors); $totalDonors = empty($donors) ? 0 : Donor::query()->count(); diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index ad15b9ef9a..9df47c0147 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -12,16 +12,6 @@ */ class RegisterDonorRoutes { - const SORTABLE_COLUMNS = [ - 'id', - 'createdAt', - 'name', - 'firstName', - 'lastName', - 'totalAmountDonated', - 'totalNumberOfDonations', - ]; - /** * @var DonorRequestController */ @@ -103,38 +93,33 @@ public function registerGetDonors() 'maximum' => 100, ], 'sort' => [ - 'validate_callback' => function ($param) { - if (empty($param)) { - return true; - } - - return in_array($param, self::SORTABLE_COLUMNS, true); - }, + 'type' => 'string', 'default' => 'id', + 'enum' => [ + 'id', + 'createdAt', + 'name', + 'firstName', + 'lastName', + 'totalAmountDonated', + 'totalNumberOfDonations', + ], ], 'direction' => [ - 'validate_callback' => function ($param) { - if (empty($param)) { - return true; - } - - return in_array(strtoupper($param), ['ASC', 'DESC'], true); - }, + 'type' => 'string', 'default' => 'ASC', + 'enum' => ['ASC', 'DESC'], ], 'onlyWithDonations' => [ 'type' => 'boolean', - 'required' => false, 'default' => true, ], 'campaignId' => [ 'type' => 'integer', - 'required' => false, 'default' => 0, ], 'hideAnonymousDonors' => [ 'type' => 'boolean', - 'required' => false, 'default' => true, ], ], From 9fd33fff0ac96cb874c7794dac07f325e9f83b36 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 12:08:16 -0300 Subject: [PATCH 35/43] refactor: change default sort direction --- src/Donations/Routes/RegisterDonationRoutes.php | 2 +- src/Donors/Routes/RegisterDonorRoutes.php | 2 +- tests/Unit/Donations/Routes/GetDonationsRouteTest.php | 4 ++++ tests/Unit/Donors/Routes/GetDonorsRouteTest.php | 5 +++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index cd60272be3..b912683781 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -109,7 +109,7 @@ public function registerGetDonations() ], 'direction' => [ 'type' => 'string', - 'default' => 'ASC', + 'default' => 'DESC', 'enum' => ['ASC', 'DESC'], ], 'campaignId' => [ diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index 9df47c0147..5a44d58a1e 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -107,7 +107,7 @@ public function registerGetDonors() ], 'direction' => [ 'type' => 'string', - 'default' => 'ASC', + 'default' => 'DESC', 'enum' => ['ASC', 'DESC'], ], 'onlyWithDonations' => [ diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 09228aeace..a3cdc728a9 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -110,6 +110,7 @@ public function testGetDonationsWithPagination() [ 'page' => 1, 'per_page' => 1, + 'direction' => 'ASC', ] ); $response = $this->dispatchRequest($request); @@ -128,6 +129,7 @@ public function testGetDonationsWithPagination() [ 'page' => 2, 'per_page' => 1, + 'direction' => 'ASC', ] ); $response = $this->dispatchRequest($request); @@ -166,6 +168,7 @@ public function testGetDonationsByCampaignId() $request->set_query_params( [ 'campaignId' => $campaign->id, + 'direction' => 'ASC', ] ); $response = $this->dispatchRequest($request); @@ -230,6 +233,7 @@ public function testGetDonationsShouldReturnAnonymousDonations() $request->set_query_params( [ 'hideAnonymousDonations' => false, + 'direction' => 'ASC', ] ); diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index f4ef285d3f..fbfb5394b5 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -152,6 +152,7 @@ public function testGetDonorsShouldReturnDonorsWithOrWithoutDonations() $request->set_query_params( [ 'onlyWithDonations' => false, + 'direction' => 'ASC', ] ); @@ -189,6 +190,7 @@ public function testGetDonorsWithPagination() 'onlyWithDonations' => false, 'page' => 1, 'per_page' => 1, + 'direction' => 'ASC', ] ); @@ -208,6 +210,7 @@ public function testGetDonorsWithPagination() 'onlyWithDonations' => false, 'page' => 2, 'per_page' => 1, + 'direction' => 'ASC', ] ); $response = $this->dispatchRequest($request); @@ -243,6 +246,7 @@ public function testGetDonorsByCampaignId() $request->set_query_params( [ 'campaignId' => $campaign->id, + 'direction' => 'ASC', ] ); $response = $this->dispatchRequest($request); @@ -303,6 +307,7 @@ public function testGetDonorsShouldReturnAnonymousDonors() $request->set_query_params( [ 'hideAnonymousDonors' => false, + 'direction' => 'ASC', ] ); From b508a7f08c77f038a8191672d5b6fa7f39f74754 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 12:10:35 -0300 Subject: [PATCH 36/43] tests: change helper method names --- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index fbfb5394b5..573fdbdca4 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -111,7 +111,7 @@ public function testGetDonorsShouldReturnOnlyDonorsWithDonations() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); + $donor1 = $this->getDonor1WithDonation($campaign->id); $donor2 = Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; @@ -144,7 +144,7 @@ public function testGetDonorsShouldReturnDonorsWithOrWithoutDonations() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); + $donor1 = $this->getDonor1WithDonation($campaign->id); $donor2 = Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; @@ -238,8 +238,8 @@ public function testGetDonorsByCampaignId() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); - $donor2 = $this->getDonor2WithDonationAssociated($campaign->id); + $donor1 = $this->getDonor1WithDonation($campaign->id); + $donor2 = $this->getDonor2WithDonation($campaign->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -272,8 +272,8 @@ public function testGetDonorsShouldNotReturnAnonymousDonors() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); - $donor2 = $this->getDonor2WithDonationAssociated($campaign->id, true); + $donor1 = $this->getDonor1WithDonation($campaign->id); + $donor2 = $this->getDonor2WithDonation($campaign->id, true); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -299,8 +299,8 @@ public function testGetDonorsShouldReturnAnonymousDonors() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonationAssociated($campaign->id); - $donor2 = $this->getDonor2WithDonationAssociated($campaign->id, true); + $donor1 = $this->getDonor1WithDonation($campaign->id); + $donor2 = $this->getDonor2WithDonation($campaign->id, true); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -339,10 +339,9 @@ public function testGetDonorsSortedByColumns($sortableColumn) /** @var Campaign $campaign2 */ $campaign2 = Campaign::factory()->create(); - - $donor1 = $this->getDonor1WithDonationAssociated($campaign1->id); - $donor2 = $this->getDonor2WithDonationAssociated($campaign1->id); - $donor3 = $this->getDonor3WithDonationAssociated($campaign2->id); + $donor1 = $this->getDonor1WithDonation($campaign1->id); + $donor2 = $this->getDonor2WithDonation($campaign1->id); + $donor3 = $this->getDonor3WithDonation($campaign2->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -455,7 +454,7 @@ public function sortableColumnsDataProvider(): array * * @throws Exception */ - private function getDonor1WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor + private function getDonor1WithDonation(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation1 */ $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); @@ -479,7 +478,7 @@ private function getDonor1WithDonationAssociated(int $campaignId, bool $anonymou * * @throws Exception */ - private function getDonor2WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor + private function getDonor2WithDonation(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation2 */ $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); @@ -503,7 +502,7 @@ private function getDonor2WithDonationAssociated(int $campaignId, bool $anonymou * * @throws Exception */ - private function getDonor3WithDonationAssociated(int $campaignId, bool $anonymous = false): Donor + private function getDonor3WithDonation(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation3 */ $donation3 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); From 35dc9ca9b9bf39b2a487c4428ebbb355526f9ef9 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 31 Jan 2025 13:45:09 -0300 Subject: [PATCH 37/43] refactor: remove distinct --- src/Donors/Controllers/DonorRequestController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index bb08dcf3fe..12dbacb48a 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -75,7 +75,7 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response } // Make sure the donation is valid - $query->distinct()->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { + $query->whereIn('donationmeta1.donation_id', function (QueryBuilder $builder) { $builder ->select('ID') ->from('posts') From ed34c899c01c02c7980802de61fab60d1ed1e907 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 7 Feb 2025 15:19:53 -0300 Subject: [PATCH 38/43] refactor: add mode parameter --- .../Controllers/DonationRequestController.php | 3 +- .../Routes/RegisterDonationRoutes.php | 5 ++ .../Controllers/DonorRequestController.php | 4 +- src/Donors/Routes/RegisterDonorRoutes.php | 5 ++ .../Routes/GetDonationsRouteTest.php | 68 +++++++++---------- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 19 +++++- 6 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 4584e57d26..64abe785ee 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -38,6 +38,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response $perPage = $request->get_param('per_page'); $sortColumn = $this->getSortColumn($request->get_param('sort')); $sortDirection = $request->get_param('direction'); + $mode = $request->get_param('mode'); $query = Donation::query(); @@ -52,7 +53,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response } // Include only current payment "mode" - $query->where('give_donationmeta_attach_meta_mode.meta_value', give_is_test_mode() ? 'test' : 'live'); + $query->where('give_donationmeta_attach_meta_mode.meta_value', $mode); // Include only valid statuses $query->whereIn('post_status', ['publish', 'give_subscription']); diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index b912683781..d643d7a383 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -112,6 +112,11 @@ public function registerGetDonations() 'default' => 'DESC', 'enum' => ['ASC', 'DESC'], ], + 'mode' => [ + 'type' => 'string', + 'default' => 'live', + 'enum' => ['live', 'test'], + ], 'campaignId' => [ 'type' => 'integer', 'default' => 0, diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 12dbacb48a..92861b0267 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -41,18 +41,18 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response $perPage = $request->get_param('per_page'); $sortColumn = $this->getSortColumn($request->get_param('sort')); $sortDirection = $request->get_param('direction'); + $mode = $request->get_param('mode'); $query = Donor::query(); // Donors only can be donors if they have donations associated with them if ($request->get_param('onlyWithDonations')) { - $query->join(function (JoinQueryBuilder $builder) { + $query->join(function (JoinQueryBuilder $builder) use ($mode) { // The donationmeta1.donation_id should be used in other "donationmeta" joins to make sure we are retrieving data from the proper donation $builder->innerJoin('give_donationmeta', 'donationmeta1') ->joinRaw("ON donationmeta1.meta_key = '" . DonationMetaKeys::DONOR_ID . "' AND donationmeta1.meta_value = ID"); // Include only current payment "mode" - $mode = give_is_test_mode() ? 'test' : 'live'; $builder->innerJoin('give_donationmeta', 'donationmeta2') ->joinRaw("ON donationmeta2.meta_key = '" . DonationMetaKeys::MODE . "' AND donationmeta2.meta_value = '{$mode}' AND donationmeta2.donation_id = donationmeta1.donation_id"); }); diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index 5a44d58a1e..a0acac3449 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -110,6 +110,11 @@ public function registerGetDonors() 'default' => 'DESC', 'enum' => ['ASC', 'DESC'], ], + 'mode' => [ + 'type' => 'string', + 'default' => 'live', + 'enum' => ['live', 'test'], + ], 'onlyWithDonations' => [ 'type' => 'boolean', 'default' => true, diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index a3cdc728a9..23b6cc7745 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -6,6 +6,7 @@ use Give\Campaigns\Models\Campaign; use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationMetaKeys; +use Give\Donations\ValueObjects\DonationMode; use Give\Donations\ValueObjects\DonationRoute; use Give\Donations\ValueObjects\DonationStatus; use Give\Donors\ValueObjects\DonorRoute; @@ -29,7 +30,7 @@ class GetDonationsRouteTest extends RestApiTestCase */ public function testGetDonationsShouldNotReturnSensitiveData() { - Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $this->createDonation1(); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -67,7 +68,7 @@ public function testGetDonationsShouldReturnSensitiveData() ); wp_set_current_user($newAdminUser); - Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $this->createDonation1(); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -97,11 +98,8 @@ public function testGetDonationsWithPagination() { Donation::query()->delete(); - /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - - /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donation1 = $this->createDonation1(); + $donation2 = $this->createDonation2(); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -155,13 +153,8 @@ public function testGetDonationsByCampaignId() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); - - /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); - give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id); + $donation1 = $this->createDonation1($campaign->id); + $donation2 = $this->createDonation2($campaign->id); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -191,11 +184,10 @@ public function testGetDonationsShouldNotReturnAnonymousDonations() { Donation::query()->delete(); - /** @var Donation $donation */ - $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donation1 = $this->createDonation1(); // This anonymous donation should NOT be returned to the data array. - Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + $donation2 = $this->createDonation2(0, true); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -207,7 +199,7 @@ public function testGetDonationsShouldNotReturnAnonymousDonations() $this->assertEquals(200, $status); $this->assertEquals(1, count($data)); - $this->assertEquals($donation->id, $data[0]['id']); + $this->assertEquals($donation1->id, $data[0]['id']); } /** @@ -219,14 +211,10 @@ public function testGetDonationsShouldReturnAnonymousDonations() { Donation::query()->delete(); - /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => false]); + $donation1 = $this->createDonation1(); - /** - * This anonymous donation should be returned to the data array. - * @var Donation $donation2 - * */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + // This anonymous donation should be returned to the data array. + $donation2 = $this->createDonation2(0, true); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -266,9 +254,9 @@ public function testGetDonationsSortedByColumns($sortableColumn) $campaign2 = Campaign::factory()->create(); - $donation1 = $this->getDonation1($campaign1->id); - $donation2 = $this->getDonation2($campaign1->id); - $donation3 = $this->getDonation3($campaign2->id); + $donation1 = $this->createDonation1($campaign1->id); + $donation2 = $this->createDonation2($campaign1->id); + $donation3 = $this->createDonation3($campaign2->id); $route = '/' . DonorRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -383,7 +371,7 @@ public function sortableColumnsDataProvider(): array * * @throws Exception */ - private function getDonation1(int $campaignId, bool $anonymous = false): Donation + private function createDonation1(int $campaignId = 0, bool $anonymous = false): Donation { /** @var Donation $donation1 */ $donation1 = Donation::factory()->create([ @@ -393,8 +381,12 @@ private function getDonation1(int $campaignId, bool $anonymous = false): Donatio 'feeAmountRecovered' => new Money(10, 'USD'), 'firstName' => 'A', 'lastName' => 'A', + 'mode' => DonationMode::LIVE(), ]); - give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + + if ($campaignId) { + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + } return $donation1; } @@ -404,7 +396,7 @@ private function getDonation1(int $campaignId, bool $anonymous = false): Donatio * * @throws Exception */ - private function getDonation2(int $campaignId, bool $anonymous = false): Donation + private function createDonation2(int $campaignId = 0, bool $anonymous = false): Donation { /** @var Donation $donation2 */ $donation2 = Donation::factory()->create([ @@ -414,8 +406,12 @@ private function getDonation2(int $campaignId, bool $anonymous = false): Donatio 'feeAmountRecovered' => new Money(20, 'USD'), 'firstName' => 'B', 'lastName' => 'B', + 'mode' => DonationMode::LIVE(), ]); - give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + + if ($campaignId) { + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + } return $donation2; } @@ -425,7 +421,7 @@ private function getDonation2(int $campaignId, bool $anonymous = false): Donatio * * @throws Exception */ - private function getDonation3(int $campaignId, bool $anonymous = false): Donation + private function createDonation3(int $campaignId = 0, bool $anonymous = false): Donation { /** @var Donation $donation3 */ $donation3 = Donation::factory()->create([ @@ -435,8 +431,12 @@ private function getDonation3(int $campaignId, bool $anonymous = false): Donatio 'feeAmountRecovered' => new Money(30, 'USD'), 'firstName' => 'C', 'lastName' => 'C', + 'mode' => DonationMode::LIVE(), ]); - give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + + if ($campaignId) { + give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + } return $donation3; } diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 573fdbdca4..1c23ae5939 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -6,6 +6,7 @@ use Give\Campaigns\Models\Campaign; use Give\Donations\Models\Donation; use Give\Donations\ValueObjects\DonationMetaKeys; +use Give\Donations\ValueObjects\DonationMode; use Give\Donations\ValueObjects\DonationStatus; use Give\Donors\Models\Donor; use Give\Donors\ValueObjects\DonorRoute; @@ -457,7 +458,11 @@ public function sortableColumnsDataProvider(): array private function getDonor1WithDonation(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation1 */ - $donation1 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); + $donation1 = Donation::factory()->create([ + 'status' => DonationStatus::COMPLETE(), + 'anonymous' => $anonymous, + 'mode' => DonationMode::LIVE(), + ]); $donor1 = $donation1->donor; $donor1->firstName = 'A'; @@ -481,7 +486,11 @@ private function getDonor1WithDonation(int $campaignId, bool $anonymous = false) private function getDonor2WithDonation(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation2 */ - $donation2 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); + $donation2 = Donation::factory()->create([ + 'status' => DonationStatus::COMPLETE(), + 'anonymous' => $anonymous, + 'mode' => DonationMode::LIVE(), + ]); $donor2 = $donation2->donor; $donor2->firstName = 'B'; @@ -505,7 +514,11 @@ private function getDonor2WithDonation(int $campaignId, bool $anonymous = false) private function getDonor3WithDonation(int $campaignId, bool $anonymous = false): Donor { /** @var Donation $donation3 */ - $donation3 = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => $anonymous]); + $donation3 = Donation::factory()->create([ + 'status' => DonationStatus::COMPLETE(), + 'anonymous' => $anonymous, + 'mode' => DonationMode::LIVE(), + ]); $donor3 = $donation3->donor; $donor3->firstName = 'C'; From ded974c38462d50bb6a55d9a2d2e9aa5a7a6aa8e Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 7 Feb 2025 15:30:51 -0300 Subject: [PATCH 39/43] refactor: replace hideAnonymousDonations with includeAnonymousDonations --- src/Donations/Controllers/DonationRequestController.php | 2 +- src/Donations/Routes/RegisterDonationRoutes.php | 4 ++-- src/Donors/Controllers/DonorRequestController.php | 2 +- src/Donors/Routes/RegisterDonorRoutes.php | 4 ++-- tests/Unit/Donations/Routes/GetDonationsRouteTest.php | 2 +- tests/Unit/Donors/Routes/GetDonorsRouteTest.php | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 64abe785ee..4c0e27c20d 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -47,7 +47,7 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response $query->where('give_donationmeta_attach_meta_campaignId.meta_value', $campaignId); } - if ($request->get_param('hideAnonymousDonations')) { + if ( ! $request->get_param('includeAnonymousDonations')) { // Exclude anonymous donations from results $query->where('give_donationmeta_attach_meta_anonymous.meta_value', 0); } diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php index d643d7a383..bbc466896b 100644 --- a/src/Donations/Routes/RegisterDonationRoutes.php +++ b/src/Donations/Routes/RegisterDonationRoutes.php @@ -121,9 +121,9 @@ public function registerGetDonations() 'type' => 'integer', 'default' => 0, ], - 'hideAnonymousDonations' => [ + 'includeAnonymousDonations' => [ 'type' => 'boolean', - 'default' => true, + 'default' => false, ], ], ] diff --git a/src/Donors/Controllers/DonorRequestController.php b/src/Donors/Controllers/DonorRequestController.php index 92861b0267..e4647486da 100644 --- a/src/Donors/Controllers/DonorRequestController.php +++ b/src/Donors/Controllers/DonorRequestController.php @@ -66,7 +66,7 @@ public function getDonors(WP_REST_Request $request): WP_REST_Response }); } - if ($request->get_param('hideAnonymousDonors')) { + if ( ! $request->get_param('includeAnonymousDonations')) { // Exclude anonymous donors from results - Donors only can be excluded if they made an anonymous donation $query->join(function (JoinQueryBuilder $builder) { $builder->innerJoin('give_donationmeta', 'donationmeta4') diff --git a/src/Donors/Routes/RegisterDonorRoutes.php b/src/Donors/Routes/RegisterDonorRoutes.php index a0acac3449..eeaecd88e9 100644 --- a/src/Donors/Routes/RegisterDonorRoutes.php +++ b/src/Donors/Routes/RegisterDonorRoutes.php @@ -123,9 +123,9 @@ public function registerGetDonors() 'type' => 'integer', 'default' => 0, ], - 'hideAnonymousDonors' => [ + 'includeAnonymousDonations' => [ 'type' => 'boolean', - 'default' => true, + 'default' => false, ], ], ] diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 23b6cc7745..68236229a9 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -220,7 +220,7 @@ public function testGetDonationsShouldReturnAnonymousDonations() $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( [ - 'hideAnonymousDonations' => false, + 'includeAnonymousDonations' => true, 'direction' => 'ASC', ] ); diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 1c23ae5939..1fe746d5f0 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -307,7 +307,7 @@ public function testGetDonorsShouldReturnAnonymousDonors() $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); $request->set_query_params( [ - 'hideAnonymousDonors' => false, + 'includeAnonymousDonations' => true, 'direction' => 'ASC', ] ); From b24102722891d4b72d14821b91d02963b7de412b Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 7 Feb 2025 15:34:32 -0300 Subject: [PATCH 40/43] tests: rename helper methods --- .../Unit/Donors/Routes/GetDonorsRouteTest.php | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php index 1fe746d5f0..e9bd596bbc 100644 --- a/tests/Unit/Donors/Routes/GetDonorsRouteTest.php +++ b/tests/Unit/Donors/Routes/GetDonorsRouteTest.php @@ -112,7 +112,7 @@ public function testGetDonorsShouldReturnOnlyDonorsWithDonations() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonation($campaign->id); + $donor1 = $this->createDonor1WithDonation($campaign->id); $donor2 = Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; @@ -145,7 +145,7 @@ public function testGetDonorsShouldReturnDonorsWithOrWithoutDonations() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonation($campaign->id); + $donor1 = $this->createDonor1WithDonation($campaign->id); $donor2 = Donor::factory()->create(); $route = '/' . DonorRoute::NAMESPACE . '/donors'; @@ -239,8 +239,8 @@ public function testGetDonorsByCampaignId() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonation($campaign->id); - $donor2 = $this->getDonor2WithDonation($campaign->id); + $donor1 = $this->createDonor1WithDonation($campaign->id); + $donor2 = $this->createDonor2WithDonation($campaign->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -273,8 +273,8 @@ public function testGetDonorsShouldNotReturnAnonymousDonors() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonation($campaign->id); - $donor2 = $this->getDonor2WithDonation($campaign->id, true); + $donor1 = $this->createDonor1WithDonation($campaign->id); + $donor2 = $this->createDonor2WithDonation($campaign->id, true); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -300,8 +300,8 @@ public function testGetDonorsShouldReturnAnonymousDonors() /** @var Campaign $campaign */ $campaign = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonation($campaign->id); - $donor2 = $this->getDonor2WithDonation($campaign->id, true); + $donor1 = $this->createDonor1WithDonation($campaign->id); + $donor2 = $this->createDonor2WithDonation($campaign->id, true); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -340,9 +340,9 @@ public function testGetDonorsSortedByColumns($sortableColumn) /** @var Campaign $campaign2 */ $campaign2 = Campaign::factory()->create(); - $donor1 = $this->getDonor1WithDonation($campaign1->id); - $donor2 = $this->getDonor2WithDonation($campaign1->id); - $donor3 = $this->getDonor3WithDonation($campaign2->id); + $donor1 = $this->createDonor1WithDonation($campaign1->id); + $donor2 = $this->createDonor2WithDonation($campaign1->id); + $donor3 = $this->createDonor3WithDonation($campaign2->id); $route = '/' . DonorRoute::NAMESPACE . '/donors'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); @@ -455,7 +455,7 @@ public function sortableColumnsDataProvider(): array * * @throws Exception */ - private function getDonor1WithDonation(int $campaignId, bool $anonymous = false): Donor + private function createDonor1WithDonation(int $campaignId = 0, bool $anonymous = false): Donor { /** @var Donation $donation1 */ $donation1 = Donation::factory()->create([ @@ -472,9 +472,12 @@ private function getDonor1WithDonation(int $campaignId, bool $anonymous = false) $donor1->totalNumberOfDonations = 1; $donor1->save(); - give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::DONOR_ID, $donor1->id); + if ($campaignId) { + give()->payment_meta->update_meta($donation1->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + } + return Donor::find($donor1->id); } @@ -483,7 +486,7 @@ private function getDonor1WithDonation(int $campaignId, bool $anonymous = false) * * @throws Exception */ - private function getDonor2WithDonation(int $campaignId, bool $anonymous = false): Donor + private function createDonor2WithDonation(int $campaignId = 0, bool $anonymous = false): Donor { /** @var Donation $donation2 */ $donation2 = Donation::factory()->create([ @@ -500,9 +503,12 @@ private function getDonor2WithDonation(int $campaignId, bool $anonymous = false) $donor2->totalNumberOfDonations = 2; $donor2->save(); - give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::DONOR_ID, $donor2->id); + if ($campaignId) { + give()->payment_meta->update_meta($donation2->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + } + return Donor::find($donor2->id); } @@ -511,7 +517,7 @@ private function getDonor2WithDonation(int $campaignId, bool $anonymous = false) * * @throws Exception */ - private function getDonor3WithDonation(int $campaignId, bool $anonymous = false): Donor + private function createDonor3WithDonation(int $campaignId = 0, bool $anonymous = false): Donor { /** @var Donation $donation3 */ $donation3 = Donation::factory()->create([ @@ -528,9 +534,12 @@ private function getDonor3WithDonation(int $campaignId, bool $anonymous = false) $donor3->totalNumberOfDonations = 3; $donor3->save(); - give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::DONOR_ID, $donor3->id); + if ($campaignId) { + give()->payment_meta->update_meta($donation3->id, DonationMetaKeys::CAMPAIGN_ID, $campaignId); + } + return Donor::find($donor3->id); } } From 74d8d6ef6e1078ff05580318e304c19ea72bbd84 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 14 Feb 2025 14:11:08 -0300 Subject: [PATCH 41/43] refactor: change the routes namespace for donors, donations, and campaigns --- .../Actions/LoadCampaignsListTableAssets.php | 3 +- .../Routes/DeleteCampaignListTable.php | 3 +- .../Routes/GetCampaignsListTable.php | 3 +- src/Campaigns/ValueObjects/CampaignRoute.php | 2 +- .../Components/CampaignStats/index.tsx | 134 +++++++++--------- .../Components/RevenueChart.tsx | 77 +++++----- .../components/CampaignDetailsPage/index.tsx | 11 +- .../components/MergeCampaign/Form/index.tsx | 2 +- src/Campaigns/resources/entity.ts | 8 +- src/Donations/ValueObjects/DonationRoute.php | 2 +- src/Donors/ValueObjects/DonorRoute.php | 2 +- 11 files changed, 119 insertions(+), 128 deletions(-) diff --git a/src/Campaigns/Actions/LoadCampaignsListTableAssets.php b/src/Campaigns/Actions/LoadCampaignsListTableAssets.php index 03f3345c2e..08ad26ae47 100644 --- a/src/Campaigns/Actions/LoadCampaignsListTableAssets.php +++ b/src/Campaigns/Actions/LoadCampaignsListTableAssets.php @@ -3,6 +3,7 @@ namespace Give\Campaigns\Actions; use Give\Campaigns\ListTable\CampaignsListTable; +use Give\Campaigns\ValueObjects\CampaignRoute; use Give\Framework\Support\Facades\Scripts\ScriptAsset; /** @@ -28,7 +29,7 @@ public function __invoke() wp_localize_script($handleName, 'GiveCampaignsListTable', [ - 'apiRoot' => esc_url_raw(rest_url('give-api/v2/campaigns/list-table')), + 'apiRoot' => esc_url_raw(rest_url(CampaignRoute::NAMESPACE . '/campaigns/list-table')), 'apiNonce' => wp_create_nonce('wp_rest'), 'table' => give(CampaignsListTable::class)->toArray(), 'adminUrl' => admin_url(), diff --git a/src/Campaigns/Routes/DeleteCampaignListTable.php b/src/Campaigns/Routes/DeleteCampaignListTable.php index 616f095d80..a0b711e8f1 100644 --- a/src/Campaigns/Routes/DeleteCampaignListTable.php +++ b/src/Campaigns/Routes/DeleteCampaignListTable.php @@ -5,6 +5,7 @@ use Give\API\RestRoute; use Give\Campaigns\ListTable\CampaignsListTable; use Give\Campaigns\Repositories\CampaignRepository; +use Give\Campaigns\ValueObjects\CampaignRoute; use Give\Framework\Exceptions\Primitives\Exception; use WP_Error; use WP_REST_Request; @@ -37,7 +38,7 @@ class DeleteCampaignListTable implements RestRoute public function registerRoute() { register_rest_route( - 'give-api/v2', + CampaignRoute::NAMESPACE, $this->endpoint, [ [ diff --git a/src/Campaigns/Routes/GetCampaignsListTable.php b/src/Campaigns/Routes/GetCampaignsListTable.php index e179ab3450..b041460d61 100644 --- a/src/Campaigns/Routes/GetCampaignsListTable.php +++ b/src/Campaigns/Routes/GetCampaignsListTable.php @@ -6,6 +6,7 @@ use Give\Campaigns\ListTable\CampaignsListTable; use Give\Campaigns\Models\Campaign; use Give\Campaigns\Repositories\CampaignRepository; +use Give\Campaigns\ValueObjects\CampaignRoute; use Give\Framework\QueryBuilder\QueryBuilder; use WP_Error; use WP_REST_Request; @@ -38,7 +39,7 @@ class GetCampaignsListTable implements RestRoute public function registerRoute(): void { register_rest_route( - 'give-api/v2', + CampaignRoute::NAMESPACE, $this->endpoint, [ [ diff --git a/src/Campaigns/ValueObjects/CampaignRoute.php b/src/Campaigns/ValueObjects/CampaignRoute.php index c3a430b843..32cf166494 100644 --- a/src/Campaigns/ValueObjects/CampaignRoute.php +++ b/src/Campaigns/ValueObjects/CampaignRoute.php @@ -16,7 +16,7 @@ */ class CampaignRoute extends Enum { - const NAMESPACE = 'give-api/v2'; + const NAMESPACE = 'givewp/v3'; const CAMPAIGN = 'campaigns/(?P[0-9]+)'; const CAMPAIGNS = 'campaigns'; } diff --git a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx index 1de9b3d5b8..6c965b7abd 100644 --- a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx +++ b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx @@ -1,16 +1,16 @@ import {__} from '@wordpress/i18n'; -import {useEffect, useState} from "react"; -import RevenueChart from "../RevenueChart"; -import GoalProgressChart from "../GoalProgressChart"; +import {useEffect, useState} from 'react'; +import RevenueChart from '../RevenueChart'; +import GoalProgressChart from '../GoalProgressChart'; import apiFetch from '@wordpress/api-fetch'; -import { addQueryArgs } from '@wordpress/url'; +import {addQueryArgs} from '@wordpress/url'; import HeaderText from '../HeaderText'; import HeaderSubText from '../HeaderSubText'; -import DefaultFormWidget from "../DefaultForm"; -import {GiveCampaignDetails} from "@givewp/campaigns/admin/components/CampaignDetailsPage/types"; +import DefaultFormWidget from '../DefaultForm'; +import {GiveCampaignDetails} from '@givewp/campaigns/admin/components/CampaignDetailsPage/types'; import {useCampaignEntityRecord} from '@givewp/campaigns/utils'; -import styles from "./styles.module.scss" +import styles from './styles.module.scss'; const campaignId = new URLSearchParams(window.location.search).get('id'); @@ -18,72 +18,77 @@ declare const window: { GiveCampaignDetails: GiveCampaignDetails; } & Window; -const pluck = (array: any[], property: string) => array.map(element => element[property]) +const pluck = (array: any[], property: string) => array.map((element) => element[property]); const filterOptions = [ - { label: __('Today', 'give'), value: 1, description: __('from today', 'give') }, - { label: __('Last 7 days', 'give'), value: 7, description: __('from the last 7 days', 'give') }, - { label: __('Last 30 days', 'give'), value: 30, description: __('from the last 30 days', 'give') }, - { label: __('Last 90 days', 'give'), value: 90, description: __('from the last 90 days', 'give') }, - { label: __('All-time', 'give'), value: 0, description: __('total for all-time', 'give') }, -] + {label: __('Today', 'give'), value: 1, description: __('from today', 'give')}, + {label: __('Last 7 days', 'give'), value: 7, description: __('from the last 7 days', 'give')}, + {label: __('Last 30 days', 'give'), value: 30, description: __('from the last 30 days', 'give')}, + {label: __('Last 90 days', 'give'), value: 90, description: __('from the last 90 days', 'give')}, + {label: __('All-time', 'give'), value: 0, description: __('total for all-time', 'give')}, +]; const currency = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', -}) +}); const CampaignStats = () => { - const [dayRange, setDayRange] = useState(null); const [stats, setStats] = useState([]); const {campaign} = useCampaignEntityRecord(); useEffect(() => { - onDayRangeChange(0) - }, []) + onDayRangeChange(0); + }, []); const onDayRangeChange = async (days: number) => { - setDayRange(days) + setDayRange(days); - apiFetch({path: addQueryArgs( '/give-api/v2/campaigns/' + campaignId +'/statistics', {rangeInDays: days} ) } ) - .then(setStats); - } + apiFetch({path: addQueryArgs('/givewp/v3/campaigns/' + campaignId + '/statistics', {rangeInDays: days})}).then( + setStats + ); + }; - const widgetDescription = filterOptions.find(option => option.value === dayRange)?.description + const widgetDescription = filterOptions.find((option) => option.value === dayRange)?.description; return ( <>
- - - - + + + +
- ) -} + ); +}; const FooterText = ({children}) => { - return ( -
- {children} -
- ) -} + return
{children}
; +}; const DisplayText = ({children}) => { - return ( -
- {children} -
- ) -} + return
{children}
; +}; const StatWidget = ({label, values, description, formatter = null}) => { return ( @@ -92,41 +97,32 @@ const StatWidget = ({label, values, description, formatter = null}) => { {label}
- - {formatter?.format(values[0]) ?? values[0]} - - {!! values[1] && ( - - )} + {formatter?.format(values[0]) ?? values[0]} + {!!values[1] && }
{description}
- ) -} + ); +}; const PercentChangePill = ({value, comparison}) => { + const change = Math.round(100 * ((value - comparison) / comparison)) ?? 0; - const change = Math.round(100 * ((value - comparison) / comparison)) ?? 0 - - const [color, backgroundColor, symbol] = change == 0 - ? ['#060c1a', '#f2f2f2', '⯈'] - : change > 0 + const [color, backgroundColor, symbol] = + change == 0 + ? ['#060c1a', '#f2f2f2', '⯈'] + : change > 0 ? ['#2d802f', '#f2fff3', '⯅'] - : ['#e35f45', '#fff4f2', '⯆'] + : ['#e35f45', '#fff4f2', '⯆']; return ( - + {symbol} {Math.abs(change)}% - ) - -} - + ); +}; const RevenueWidget = () => { return ( @@ -138,10 +134,9 @@ const RevenueWidget = () => { ); -} +}; const GoalProgressWidget = () => { - const {campaign} = useCampaignEntityRecord(); return ( @@ -152,8 +147,8 @@ const GoalProgressWidget = () => { - ) -} + ); +}; const DateRangeFilters = ({options, onSelect, selected}) => { return ( @@ -168,8 +163,7 @@ const DateRangeFilters = ({options, onSelect, selected}) => { ))} - ) -} - + ); +}; export default CampaignStats; diff --git a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/RevenueChart.tsx b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/RevenueChart.tsx index 5beed0ab01..385c0e1f8d 100644 --- a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/RevenueChart.tsx +++ b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/RevenueChart.tsx @@ -1,41 +1,42 @@ -import React, {useEffect, useState} from "react"; -import Chart from "react-apexcharts"; -import apiFetch from "@wordpress/api-fetch"; -import {addQueryArgs} from "@wordpress/url"; +import React, {useEffect, useState} from 'react'; +import Chart from 'react-apexcharts'; +import apiFetch from '@wordpress/api-fetch'; +import {addQueryArgs} from '@wordpress/url'; const campaignId = new URLSearchParams(window.location.search).get('id'); const RevenueChart = () => { - const [max, setMax] = useState(0); const [categories, setCategories] = useState([]); - const [series, setSeries] = useState([{name: "Revenue", data: []}]); + const [series, setSeries] = useState([{name: 'Revenue', data: []}]); useEffect(() => { - apiFetch({path: addQueryArgs( '/give-api/v2/campaigns/' + campaignId +'/revenue' ) } ) - .then((data: {date: string, amount: number}[]) => { - - setMax(Math.max(...data.map(item => item.amount)) * 1.1) + apiFetch({path: addQueryArgs('/givewp/v3/campaigns/' + campaignId + '/revenue')}).then( + (data: {date: string; amount: number}[]) => { + setMax(Math.max(...data.map((item) => item.amount)) * 1.1); - setCategories(data.map(item => item.date)) + setCategories(data.map((item) => item.date)); - setSeries([{ - name: "Revenue", - data: data.map(item => item.amount) - }]) - }); - }, []) + setSeries([ + { + name: 'Revenue', + data: data.map((item) => item.amount), + }, + ]); + } + ); + }, []); const options = { chart: { - id: "campaign-revenue", + id: 'campaign-revenue', zoom: { - enabled: false + enabled: false, }, }, xaxis: { categories, - type: 'datetime' as "datetime" | "category" | "numeric", + type: 'datetime' as 'datetime' | 'category' | 'numeric', }, yaxis: { max, @@ -43,8 +44,14 @@ const RevenueChart = () => { stroke: { color: ['#60a1e2'], width: 1.5, - curve: 'smooth' as "straight" | "smooth" | "monotoneCubic" | "stepline" | "linestep" | ("straight" | "smooth" | "monotoneCubic" | "stepline" | "linestep")[], - lineCap: 'butt' as "butt" | "square" | "round", + curve: 'smooth' as + | 'straight' + | 'smooth' + | 'monotoneCubic' + | 'stepline' + | 'linestep' + | ('straight' | 'smooth' | 'monotoneCubic' | 'stepline' | 'linestep')[], + lineCap: 'butt' as 'butt' | 'square' | 'round', }, dataLabels: { enabled: false, @@ -57,35 +64,29 @@ const RevenueChart = () => { { offset: 0, color: '#eee', - opacity: 1 + opacity: 1, }, { offset: 0.6, color: '#b7d4f2', - opacity: 50 + opacity: 50, }, { offset: 100, color: '#f0f7ff', - opacity: 1 - } + opacity: 1, + }, ], ], - } - } + }, + }, }; return ( <> - + - ) -} + ); +}; -export default RevenueChart +export default RevenueChart; diff --git a/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx b/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx index 123b6b673e..e1e86f4abf 100644 --- a/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx +++ b/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx @@ -65,7 +65,7 @@ export default function CampaignsDetailsPage({campaignId}) { useEffect(() => { apiFetch({ - path: `/give-api/v2/campaigns/${campaignId}`, + path: `/givewp/v3/campaigns/${campaignId}`, method: 'OPTIONS', }).then(({schema}: {schema: JSONSchemaType}) => { setResolver({ @@ -74,12 +74,7 @@ export default function CampaignsDetailsPage({campaignId}) { }); }, []); - const { - campaign, - hasResolved, - save, - edit, - } = useCampaignEntityRecord(campaignId); + const {campaign, hasResolved, save, edit} = useCampaignEntityRecord(campaignId); const methods = useForm({ mode: 'onBlur', @@ -107,7 +102,7 @@ export default function CampaignsDetailsPage({campaignId}) { id: 'update-archive-notice', type: 'warning', onDismiss: () => updateStatus('draft'), - content: (onDismiss: Function) => + content: (onDismiss: Function) => , }); }, [campaign?.status]); diff --git a/src/Campaigns/resources/admin/components/MergeCampaign/Form/index.tsx b/src/Campaigns/resources/admin/components/MergeCampaign/Form/index.tsx index bc99544593..c6a9cb35d9 100644 --- a/src/Campaigns/resources/admin/components/MergeCampaign/Form/index.tsx +++ b/src/Campaigns/resources/admin/components/MergeCampaign/Form/index.tsx @@ -48,7 +48,7 @@ export default function MergeCampaignsForm({isOpen, handleClose, title, campaign try { const response = await apiFetch({ - path: addQueryArgs('/give-api/v2/campaigns/' + destinationCampaignId + '/merge', { + path: addQueryArgs('/givewp/v3/campaigns/' + destinationCampaignId + '/merge', { campaignsToMergeIds: campaignsToMergeIds, }), method: 'PATCH', diff --git a/src/Campaigns/resources/entity.ts b/src/Campaigns/resources/entity.ts index cb4409ddcb..67c518e735 100644 --- a/src/Campaigns/resources/entity.ts +++ b/src/Campaigns/resources/entity.ts @@ -8,12 +8,10 @@ dispatch(coreStore).addEntities([ { name: 'campaign', kind: 'givewp', - baseURL: '/give-api/v2/campaigns', + baseURL: '/givewp/v3/campaigns', baseURLParams: {}, plural: 'campaigns', label: __('Campaign', 'give'), - supportsPagination: true - } + supportsPagination: true, + }, ]); - - diff --git a/src/Donations/ValueObjects/DonationRoute.php b/src/Donations/ValueObjects/DonationRoute.php index 13e7add090..756a37f520 100644 --- a/src/Donations/ValueObjects/DonationRoute.php +++ b/src/Donations/ValueObjects/DonationRoute.php @@ -16,7 +16,7 @@ */ class DonationRoute extends Enum { - const NAMESPACE = 'give-api/v2'; + const NAMESPACE = 'givewp/v3'; const DONATION = 'donations/(?P[0-9]+)'; const DONATIONS = 'donations'; } diff --git a/src/Donors/ValueObjects/DonorRoute.php b/src/Donors/ValueObjects/DonorRoute.php index 804e089286..552e7de2db 100644 --- a/src/Donors/ValueObjects/DonorRoute.php +++ b/src/Donors/ValueObjects/DonorRoute.php @@ -16,7 +16,7 @@ */ class DonorRoute extends Enum { - const NAMESPACE = 'give-api/v2'; + const NAMESPACE = 'givewp/v3'; const DONOR = 'donors/(?P[0-9]+)'; const DONORS = 'donors'; } From 7def493999265a45cb5d79574386fcdeadaf1bf9 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 14 Feb 2025 15:09:58 -0300 Subject: [PATCH 42/43] refactor prevent access to "anonymous data" --- .../Controllers/DonationRequestController.php | 35 +++++++++++----- .../Donations/Routes/GetDonationRouteTest.php | 40 ++++++++++++++++++- .../Routes/GetDonationsRouteTest.php | 35 ++++++++++++++++ 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php index 4c0e27c20d..233cfed589 100644 --- a/src/Donations/Controllers/DonationRequestController.php +++ b/src/Donations/Controllers/DonationRequestController.php @@ -106,20 +106,33 @@ public function getDonations(WP_REST_Request $request): WP_REST_Response */ public function escDonation(Donation $donation): array { - $donation = $donation->toArray(); + if (current_user_can('manage_options')) { + return $donation->toArray(); + } + + $sensitiveData = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; - if ( ! current_user_can('manage_options')) { - $sensitiveProperties = [ - 'donorIp', - 'email', - 'phone', - 'billingAddress', + if ($donation->anonymous) { + $anonymousData = [ + 'donorId', + 'honorific', + 'firstName', + 'lastName', + 'company', ]; - foreach ($sensitiveProperties as $property) { - if (array_key_exists($property, $donation)) { - unset($donation[$property]); - } + $sensitiveData = array_merge($sensitiveData, $anonymousData); + } + + $donation = $donation->toArray(); + foreach ($sensitiveData as $property) { + if (array_key_exists($property, $donation)) { + unset($donation[$property]); } } diff --git a/tests/Unit/Donations/Routes/GetDonationRouteTest.php b/tests/Unit/Donations/Routes/GetDonationRouteTest.php index 55262d372b..ed69dc9e6a 100644 --- a/tests/Unit/Donations/Routes/GetDonationRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationRouteTest.php @@ -56,7 +56,7 @@ public function testGetDonationShouldNotReturnSensitiveData() $status = $response->get_status(); $data = $response->get_data(); - $sensitiveProperties = [ + $sensitiveData = [ 'donorIp', 'email', 'phone', @@ -64,6 +64,42 @@ public function testGetDonationShouldNotReturnSensitiveData() ]; $this->assertEquals(200, $status); - $this->assertEmpty(array_intersect_key($data, $sensitiveProperties)); + $this->assertEmpty(array_intersect_key($data, array_flip($sensitiveData))); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationShouldNotReturnSensitiveAndAnonymousData() + { + /** @var Donation $donation */ + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + + $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveAndAnonymousData = [ + // sensitive data + 'donorIp', + 'email', + 'phone', + 'billingAddress', + // anonymous data + 'donorId', + 'honorific', + 'firstName', + 'lastName', + 'company', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data, array_flip($sensitiveAndAnonymousData))); } } diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index 68236229a9..fe585943f9 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -51,6 +51,41 @@ public function testGetDonationsShouldNotReturnSensitiveData() $this->assertEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); } + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsShouldNotReturnSensitiveAndAnonymousData() + { + $this->createDonation1(); + + $route = '/' . DonationRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveAndAnonymousData = [ + // sensitive data + 'donorIp', + 'email', + 'phone', + 'billingAddress', + // anonymous data + 'donorId', + 'honorific', + 'firstName', + 'lastName', + 'company', + ]; + + $this->assertEquals(200, $status); + $this->assertEmpty(array_intersect_key($data[0], array_flip($sensitiveAndAnonymousData))); + } + /** * @unreleased * From 238fd0a69a59f9bb86b6d7c936e69dcb1e4f8f99 Mon Sep 17 00:00:00 2001 From: Glauber Silva Date: Fri, 14 Feb 2025 15:33:58 -0300 Subject: [PATCH 43/43] tests: add new methods to check anonymous data return --- .../Donations/Routes/GetDonationRouteTest.php | 97 +++++++++++++++++++ .../Routes/GetDonationsRouteTest.php | 68 ++++++++++++- 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Donations/Routes/GetDonationRouteTest.php b/tests/Unit/Donations/Routes/GetDonationRouteTest.php index ed69dc9e6a..01dcd26e31 100644 --- a/tests/Unit/Donations/Routes/GetDonationRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationRouteTest.php @@ -67,6 +67,45 @@ public function testGetDonationShouldNotReturnSensitiveData() $this->assertEmpty(array_intersect_key($data, array_flip($sensitiveData))); } + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationShouldReturnSensitiveData() + { + $newAdminUser = $this->factory()->user->create( + [ + 'role' => 'administrator', + 'user_login' => 'admin38974238473824', + 'user_pass' => 'admin38974238473824', + 'user_email' => 'admin38974238473824@test.com', + ] + ); + wp_set_current_user($newAdminUser); + + /** @var Donation $donation */ + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE()]); + + $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveData = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; + + $this->assertEquals(200, $status); + $this->assertNotEmpty(array_intersect_key($data, array_flip($sensitiveData))); + } + /** * @unreleased * @@ -80,6 +119,12 @@ public function testGetDonationShouldNotReturnSensitiveAndAnonymousData() $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'includeAnonymousDonations' => true, + ] + ); + $response = $this->dispatchRequest($request); $status = $response->get_status(); @@ -102,4 +147,56 @@ public function testGetDonationShouldNotReturnSensitiveAndAnonymousData() $this->assertEquals(200, $status); $this->assertEmpty(array_intersect_key($data, array_flip($sensitiveAndAnonymousData))); } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationShouldReturnSensitiveAndAnonymousData() + { + $newAdminUser = $this->factory()->user->create( + [ + 'role' => 'administrator', + 'user_login' => 'admin38974238473824', + 'user_pass' => 'admin38974238473824', + 'user_email' => 'admin38974238473824@test.com', + ] + ); + wp_set_current_user($newAdminUser); + + /** @var Donation $donation */ + $donation = Donation::factory()->create(['status' => DonationStatus::COMPLETE(), 'anonymous' => true]); + + $route = '/' . DonationRoute::NAMESPACE . '/donations/' . $donation->id; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $request->set_query_params( + [ + 'includeAnonymousDonations' => true, + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + + $sensitiveAndAnonymousData = [ + // sensitive data + 'donorIp', + 'email', + 'phone', + 'billingAddress', + // anonymous data + 'donorId', + 'honorific', + 'firstName', + 'lastName', + 'company', + ]; + + $this->assertEquals(200, $status); + $this->assertNotEmpty(array_intersect_key($data, array_flip($sensitiveAndAnonymousData))); + } } diff --git a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php index fe585943f9..da7481e5b9 100644 --- a/tests/Unit/Donations/Routes/GetDonationsRouteTest.php +++ b/tests/Unit/Donations/Routes/GetDonationsRouteTest.php @@ -56,8 +56,18 @@ public function testGetDonationsShouldNotReturnSensitiveData() * * @throws Exception */ - public function testGetDonationsShouldNotReturnSensitiveAndAnonymousData() + public function testGetDonationsShouldReturnSensitiveData() { + $newAdminUser = $this->factory()->user->create( + [ + 'role' => 'administrator', + 'user_login' => 'admin38974238473824', + 'user_pass' => 'admin38974238473824', + 'user_email' => 'admin38974238473824@test.com', + ] + ); + wp_set_current_user($newAdminUser); + $this->createDonation1(); $route = '/' . DonationRoute::NAMESPACE . '/donations'; @@ -68,6 +78,40 @@ public function testGetDonationsShouldNotReturnSensitiveAndAnonymousData() $status = $response->get_status(); $data = $response->get_data(); + $sensitiveProperties = [ + 'donorIp', + 'email', + 'phone', + 'billingAddress', + ]; + + $this->assertEquals(200, $status); + $this->assertNotEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testGetDonationsShouldNotReturnSensitiveAndAnonymousData() + { + $this->createDonation1(0, true); + + $route = '/' . DonationRoute::NAMESPACE . '/donations'; + $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + + $request->set_query_params( + [ + 'includeAnonymousDonations' => true, + ] + ); + + $response = $this->dispatchRequest($request); + + $status = $response->get_status(); + $data = $response->get_data(); + $sensitiveAndAnonymousData = [ // sensitive data 'donorIp', @@ -91,7 +135,7 @@ public function testGetDonationsShouldNotReturnSensitiveAndAnonymousData() * * @throws Exception */ - public function testGetDonationsShouldReturnSensitiveData() + public function testGetDonationsShouldReturnSensitiveAndAnonymousData() { $newAdminUser = $this->factory()->user->create( [ @@ -103,25 +147,39 @@ public function testGetDonationsShouldReturnSensitiveData() ); wp_set_current_user($newAdminUser); - $this->createDonation1(); + $this->createDonation1(0, true); $route = '/' . DonationRoute::NAMESPACE . '/donations'; $request = new WP_REST_Request(WP_REST_Server::READABLE, $route); + $request->set_query_params( + [ + 'includeAnonymousDonations' => true, + ] + ); + $response = $this->dispatchRequest($request); + $status = $response->get_status(); $data = $response->get_data(); - $sensitiveProperties = [ + $sensitiveAndAnonymousData = [ + // sensitive data 'donorIp', 'email', 'phone', 'billingAddress', + // anonymous data + 'donorId', + 'honorific', + 'firstName', + 'lastName', + 'company', ]; $this->assertEquals(200, $status); - $this->assertNotEmpty(array_intersect_key($data[0], array_flip($sensitiveProperties))); + $this->assertNotEmpty(array_intersect_key($data[0], array_flip($sensitiveAndAnonymousData))); } /**