-
Notifications
You must be signed in to change notification settings - Fork 195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: add new routes for donations and donors #7699
Open
glaubersilva
wants to merge
55
commits into
epic/campaigns
Choose a base branch
from
feature/donations-donors-controllers
base: epic/campaigns
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 11 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
e22047b
feature: add get routes for donations
glaubersilva a2d2695
tests: add test for donations pagination
glaubersilva 74af706
tests: add more asserts
glaubersilva ebd4d57
tests: add GetDonationRouteTest class
glaubersilva 6996169
feature: add get endpoints for donors
glaubersilva aaecb8a
tests: add GetDonorRouteTest class
glaubersilva 7505388
tests: add testGetDonorsWithPagination method
glaubersilva 0850304
refactor: change permissions callback
glaubersilva df1697b
refactor: tweaks and polishments
glaubersilva a1900dd
tests: tweak property comments
glaubersilva 5a1bb94
fix: wrong rest_url path
glaubersilva bfe2a6f
refactor: restrict access to sensitive data
glaubersilva 4dcb2d2
refactor: uncomment code
glaubersilva 7a086f5
refactor: add where conditions for donation mode and status
glaubersilva 4fca5af
refactor: use query method
glaubersilva 0aa85a1
refactor: remove unnecessary distinct clause
glaubersilva 3ada42b
feature: add hideAnonymousDonations parameter
glaubersilva 8ad5a7d
refactor: remove unnecessary select
glaubersilva 205d1a1
refactor: improve query readability and consider status and payment mode
glaubersilva 811a6a2
feature: add hideAnonymousDonors parameter
glaubersilva 9a8a324
fix: broken unit tests
glaubersilva 7e83d0d
feature: add onlyWithDonations param
glaubersilva e57c51e
doc: add comment
glaubersilva 4ae26ea
refactor: remove unnecessary distinct
glaubersilva c64e98c
refactor: add sort and direction parameters
glaubersilva 7aa9903
tests: add testGetDonorsSortedByTotalAmountDonated method
glaubersilva 7fee060
tests: add sortableColumnsDataProvider and refactor test to use it
glaubersilva e0c0cc3
tests: simplify logic
glaubersilva b54e0b4
tests: add testGetDonorsShouldReturnSensitiveData method
glaubersilva 783655b
tests: add new tests for the onlyWithDonations param
glaubersilva e3eb167
tests: add testGetDonationsShouldReturnSensitiveData method
glaubersilva bf7214a
refactor: simplify query
glaubersilva 563a726
tests: add testGetDonationsSortedByColumns
glaubersilva ca5c7a7
refactor: replace validate_callback with enum
glaubersilva 9fd33ff
refactor: change default sort direction
glaubersilva b508a7f
tests: change helper method names
glaubersilva 35dc9ca
refactor: remove distinct
glaubersilva ed34c89
refactor: add mode parameter
glaubersilva ded974c
refactor: replace hideAnonymousDonations with includeAnonymousDonations
glaubersilva b241027
tests: rename helper methods
glaubersilva 74d8d6e
refactor: change the routes namespace for donors, donations, and camp…
glaubersilva 7def493
refactor prevent access to "anonymous data"
glaubersilva 238fd0a
tests: add new methods to check anonymous data return
glaubersilva f839287
Merge branch 'epic/campaigns' into feature/donations-donors-controllers
glaubersilva 1ec35b7
refactor: change API namespace
glaubersilva 5bd2cc9
refactor: add anonymousDonations enum
glaubersilva 95acf68
refactor: add sensitiveData parameter
glaubersilva 06c8499
refactor: replace properties with enum classes
glaubersilva 8ff58df
refactor: add new parameters to single donation endpoint
glaubersilva 5a2433e
tests: fix broken tests
glaubersilva 183ec8c
refactor: simplify sensitive data parameter
glaubersilva c255a67
chore: remove commented code
glaubersilva b545b61
chore: remove commented code
glaubersilva 236e200
feature: add includeSensitiveData and anonymousDonations for donor an…
glaubersilva 8f7a91f
refactor: create DonorAnonymousMode enum class and rename param
glaubersilva File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php | ||
|
||
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; | ||
use WP_REST_Request; | ||
use WP_REST_Response; | ||
|
||
/** | ||
* @unreleased | ||
*/ | ||
class DonationRequestController | ||
{ | ||
/** | ||
* @unreleased | ||
* | ||
* @return WP_Error | WP_REST_Response | ||
*/ | ||
public function getDonation(WP_REST_Request $request) | ||
{ | ||
$donation = Donation::find($request->get_param('id')); | ||
|
||
if ( ! $donation) { | ||
return new WP_Error('donation_not_found', __('Donation not found', 'give'), ['status' => 404]); | ||
} | ||
|
||
return new WP_REST_Response($donation->toArray()); | ||
} | ||
|
||
/** | ||
* @unreleased | ||
*/ | ||
public function getDonations(WP_REST_Request $request): WP_REST_Response | ||
{ | ||
$page = $request->get_param('page'); | ||
$perPage = $request->get_param('per_page'); | ||
|
||
$query = give(DonationRepository::class)->prepareQuery(); | ||
|
||
if ($campaignId = $request->get_param('campaignId')) { | ||
$query->distinct()->join(function (JoinQueryBuilder $builder) { | ||
glaubersilva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$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); | ||
} | ||
|
||
$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); | ||
glaubersilva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?php | ||
|
||
namespace Give\Donations\Routes; | ||
|
||
use Give\Donations\Controllers\DonationRequestController; | ||
use Give\Donations\ValueObjects\DonationRoute; | ||
use WP_REST_Request; | ||
use WP_REST_Server; | ||
|
||
/** | ||
* @unreleased | ||
*/ | ||
class RegisterDonationRoutes | ||
{ | ||
/** | ||
* @var DonationRequestController | ||
*/ | ||
protected $donationRequestController; | ||
|
||
/** | ||
* @unreleased | ||
*/ | ||
public function __construct(DonationRequestController $donationRequestController) | ||
{ | ||
$this->donationRequestController = $donationRequestController; | ||
} | ||
|
||
/** | ||
* @unreleased | ||
*/ | ||
public function __invoke() | ||
{ | ||
$this->registerGetDonation(); | ||
$this->registerGetDonations(); | ||
} | ||
|
||
/** | ||
* Get Donation route | ||
* | ||
* @unreleased | ||
*/ | ||
public function registerGetDonation() | ||
{ | ||
register_rest_route( | ||
DonationRoute::NAMESPACE, | ||
DonationRoute::DONATION, | ||
[ | ||
[ | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => function (WP_REST_Request $request) { | ||
return $this->donationRequestController->getDonation($request); | ||
}, | ||
'permission_callback' => '__return_true', | ||
glaubersilva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
], | ||
'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, | ||
], | ||
], | ||
] | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Give\Donations\ValueObjects; | ||
|
||
use Give\Framework\Support\ValueObjects\Enum; | ||
|
||
/** | ||
* @unreleased | ||
* | ||
* @method static DonationRoute NAMESPACE() | ||
* @method static DonationRoute DONATION() | ||
* @method static DonationRoute DONATIONS() | ||
* @method bool isNamespace() | ||
* @method bool isDonation() | ||
* @method bool isDonations() | ||
*/ | ||
class DonationRoute extends Enum | ||
{ | ||
const NAMESPACE = 'give-api/v2'; | ||
const DONATION = 'donations/(?P<id>[0-9]+)'; | ||
const DONATIONS = 'donations'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
|
||
namespace Give\Donors\Controllers; | ||
|
||
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; | ||
use WP_Error; | ||
use WP_REST_Request; | ||
use WP_REST_Response; | ||
|
||
/** | ||
* @unreleased | ||
*/ | ||
class DonorRequestController | ||
{ | ||
/** | ||
* @unreleased | ||
* | ||
* @return WP_Error | WP_REST_Response | ||
*/ | ||
public function getDonor(WP_REST_Request $request) | ||
{ | ||
$donation = Donor::find($request->get_param('id')); | ||
|
||
if ( ! $donation) { | ||
return new WP_Error('donor_not_found', __('Donor 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'"); | ||
}); | ||
} | ||
glaubersilva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$query | ||
->limit($perPage) | ||
->offset(($page - 1) * $perPage); | ||
|
||
$donors = $query->getAll() ?? []; | ||
glaubersilva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$totalDonors = empty($donors) ? 0 : Donor::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(DonorRoute::DONORS) | ||
); | ||
|
||
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; | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't die on this hill, but I'm not personally a fan of separating the request registration from the callbacks. First, it's not the convention. Second, it makes it harder to figure out the expected parameters. I think it feels necessary now because there's so much query building going on, but once we abstract out the query details it will greatly reduce the length of the callback code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JasonTheAdams Yeah! I agree, I just followed this pattern because we already are using it on the campaign routes. But I think we can leave it this way right now and refactor it later in a subsequent PR to improve the code maintenance.