PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.18.0
GiveWP – Donation Plugin and Fundraising Platform v4.18.0
4.18.0 4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 All 257 releases
give / src / DonationForms / Repositories / DonationFormRepository.php

DonationFormRepository.php in GiveWP – Donation Plugin and Fundraising Platform 4.18.0, at src/DonationForms/Repositories/DonationFormRepository.php

539 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\DonationForms\Repositories;
4
5 use Closure;
6 use Give\DonationForms\Actions\ConvertDonationFormBlocksToFieldsApi;
7 use Give\DonationForms\DonationQuery;
8 use Give\DonationForms\Models\DonationForm;
9 use Give\DonationForms\ValueObjects\DonationFormMetaKeys;
10 use Give\Donations\ValueObjects\DonationMetaKeys;
11 use Give\Framework\Blocks\BlockCollection;
12 use Give\Framework\Database\DB;
13 use Give\Framework\Exceptions\Primitives\Exception;
14 use Give\Framework\Exceptions\Primitives\InvalidArgumentException;
15 use Give\Framework\FieldsAPI\DonationForm as DonationFormNode;
16 use Give\Framework\FieldsAPI\Exceptions\NameCollisionException;
17 use Give\Framework\FieldsAPI\Hidden;
18 use Give\Framework\FieldsAPI\Section;
19 use Give\Framework\Models\ModelQueryBuilder;
20 use Give\Framework\PaymentGateways\PaymentGateway;
21 use Give\Framework\PaymentGateways\PaymentGatewayRegister;
22 use Give\Framework\Support\Facades\DateTime\Temporal;
23 use Give\Helpers\Form\Utils;
24 use Give\Helpers\Hooks;
25 use Give\Log\Log;
26
27 /**
28 * @since 3.0.0
29 */
30 class DonationFormRepository
31 {
32 /**
33 * @var PaymentGatewayRegister
34 */
35 private $paymentGatewayRegister;
36
37 /**
38 * @since 3.0.0
39 *
40 * @var string[]
41 */
42 private $requiredProperties = [
43 'status',
44 'title',
45 'blocks',
46 ];
47
48 /**
49 * @since 3.0.0
50 *
51 * @param PaymentGatewayRegister $paymentGatewayRegister
52 */
53 public function __construct(PaymentGatewayRegister $paymentGatewayRegister)
54 {
55 $this->paymentGatewayRegister = $paymentGatewayRegister;
56 }
57
58 /**
59 * Get Donation Form By ID
60 *
61 * @since 3.0.0
62 *
63 * @return DonationForm|null
64 */
65 public function getById(int $id)
66 {
67 return $this->prepareQuery()
68 ->where('ID', $id)
69 ->get();
70 }
71
72 /**
73 * @since 4.18.0 Keep the original error as the previous exception when the write fails.
74 * @since 3.7.0 Add post_excerpt to the list of fields being inserted
75 * @since 3.0.0
76 *
77 * @return void
78 * @throws Exception|InvalidArgumentException
79 */
80 public function insert(DonationForm $donationForm)
81 {
82 $this->validateProperties($donationForm);
83
84 Hooks::doAction('givewp_donation_form_creating', $donationForm);
85
86 $dateCreated = Temporal::withoutMicroseconds($donationForm->createdAt ?: Temporal::getCurrentDateTime());
87 $dateCreatedFormatted = Temporal::getFormattedDateTime($dateCreated);
88
89 $donationForm->settings->pageSlug = wp_unique_post_slug(
90 $donationForm->settings->pageSlug ?: sanitize_title($donationForm->title),
91 $donationForm->id,
92 $donationForm->status->getValue(),
93 'give_forms',
94 0
95 );
96
97 DB::query('START TRANSACTION');
98
99 try {
100 DB::table('posts')
101 ->insert([
102 'post_date' => $dateCreatedFormatted,
103 'post_date_gmt' => get_gmt_from_date($dateCreatedFormatted),
104 'post_modified' => $dateCreatedFormatted,
105 'post_modified_gmt' => get_gmt_from_date($dateCreatedFormatted),
106 'post_status' => $donationForm->status->getValue(),
107 'post_type' => 'give_forms',
108 'post_excerpt' => $donationForm->settings->formExcerpt,
109 'post_parent' => 0,
110 'post_title' => $donationForm->title,
111 'post_content' => (new BlockCollection([]))->toJson(), // @todo Repurpose as form page.
112 'post_name' => $donationForm->settings->pageSlug,
113 ]);
114
115 $donationFormId = DB::last_insert_id();
116
117 DB::table('give_formmeta')
118 ->insert([
119 'form_id' => $donationFormId,
120 'meta_key' => DonationFormMetaKeys::SETTINGS()->getValue(),
121 'meta_value' => $donationForm->settings->toJson(),
122 ]);
123
124 DB::table('give_formmeta')
125 ->insert([
126 'form_id' => $donationFormId,
127 'meta_key' => DonationFormMetaKeys::FIELDS()->getValue(),
128 'meta_value' => $donationForm->blocks->toJson(),
129 ]);
130 } catch (Exception $exception) {
131 DB::query('ROLLBACK');
132
133 Log::error('Failed creating a donation form', compact('donationForm'));
134
135 throw new Exception('Failed creating a donation form', 0, $exception);
136 }
137
138 DB::query('COMMIT');
139
140 $donationForm->id = $donationFormId;
141
142 $donationForm->createdAt = $dateCreated;
143
144 if (!isset($donation->updatedAt)) {
145 $donationForm->updatedAt = $donationForm->createdAt;
146 }
147
148 Hooks::doAction('givewp_donation_form_created', $donationForm);
149 }
150
151 /**
152 * @since 4.18.0 Keep the original error as the previous exception when the write fails.
153 * @since 3.7.0 Add post_excerpt to the list of fields being updated
154 * @since 3.0.0
155 *
156 * @param DonationForm $donationForm
157 *
158 * @return void
159 * @throws Exception|InvalidArgumentException
160 */
161 public function update(DonationForm $donationForm)
162 {
163 $this->validateProperties($donationForm);
164
165 Hooks::doAction('givewp_donation_form_updating', $donationForm);
166
167 $date = Temporal::getCurrentFormattedDateForDatabase();
168
169 $donationForm->settings->pageSlug = wp_unique_post_slug(
170 $donationForm->settings->pageSlug ?: sanitize_title($donationForm->title),
171 $donationForm->id,
172 $donationForm->status->getValue(),
173 'give_forms',
174 0
175 );
176
177 DB::query('START TRANSACTION');
178
179 try {
180 DB::table('posts')
181 ->where('ID', $donationForm->id)
182 ->update([
183 'post_modified' => $date,
184 'post_modified_gmt' => get_gmt_from_date($date),
185 'post_status' => $donationForm->status->getValue(),
186 'post_title' => $donationForm->title,
187 'post_excerpt' => $donationForm->settings->formExcerpt,
188 'post_content' => (new BlockCollection([]))->toJson(), // @todo Repurpose as form page.
189 'post_name' => $donationForm->settings->pageSlug,
190 ]);
191
192 DB::table('give_formmeta')
193 ->where('form_id', $donationForm->id)
194 ->where('meta_key', DonationFormMetaKeys::SETTINGS()->getValue())
195 ->update([
196 'meta_value' => $donationForm->settings->toJson(),
197 ]);
198
199
200 DB::table('give_formmeta')
201 ->where('form_id', $donationForm->id)
202 ->where('meta_key', DonationFormMetaKeys::FIELDS()->getValue())
203 ->update([
204 'meta_value' => $donationForm->blocks->toJson(),
205 ]);
206 } catch (Exception $exception) {
207 DB::query('ROLLBACK');
208
209 Log::error('Failed updating a donation form', compact('donationForm'));
210
211 throw new Exception('Failed updating a donation form', 0, $exception);
212 }
213
214 DB::query('COMMIT');
215
216 Hooks::doAction('givewp_donation_form_updated', $donationForm);
217 }
218
219 /**
220 * @since 4.18.0 Keep the original error as the previous exception when the write fails.
221 * @since 3.0.0
222 *
223 * @throws Exception
224 */
225 public function delete(DonationForm $donationForm): bool
226 {
227 DB::query('START TRANSACTION');
228
229 Hooks::doAction('givewp_donation_form_deleting', $donationForm);
230
231 try {
232 DB::table('posts')
233 ->where('id', $donationForm->id)
234 ->delete();
235
236 DB::table('give_formmeta')
237 ->where('form_id', $donationForm->id)
238 ->delete();
239 } catch (Exception $exception) {
240 DB::query('ROLLBACK');
241
242 Log::error('Failed deleting a donation form', compact('donationForm'));
243
244 throw new Exception('Failed deleting a donation form', 0, $exception);
245 }
246
247 DB::query('COMMIT');
248
249 Hooks::doAction('givewp_donation_form_deleted', $donationForm);
250
251 return true;
252 }
253
254 /**
255 * @since 3.0.0
256 *
257 * @param DonationForm $donationForm
258 *
259 * @return void
260 */
261 private function validateProperties(DonationForm $donationForm)
262 {
263 foreach ($this->requiredProperties as $key) {
264 if (!isset($donationForm->$key)) {
265 throw new InvalidArgumentException("'$key' is required.");
266 }
267 }
268 }
269
270 /**
271 * @since 3.12.1 Prevent returning forms without the "formBuilderSettings" and "formBuilderFields" meta keys
272 * @since 3.0.0
273 *
274 * @return ModelQueryBuilder<DonationForm>
275 */
276 public function prepareQuery(): ModelQueryBuilder
277 {
278 $builder = new ModelQueryBuilder(DonationForm::class);
279
280 return $builder->from('posts', 'forms')
281 ->select(
282 ['ID', 'id'],
283 ['post_date', 'createdAt'],
284 ['post_modified', 'updatedAt'],
285 ['post_status', 'status'],
286 ['post_title', 'title'],
287 ['post_content', 'page_content'] // @todo Repurpose as form page.
288 )
289 ->attachMeta(
290 'give_formmeta',
291 'ID',
292 'form_id',
293 ...DonationFormMetaKeys::getColumnsForAttachMetaQuery()
294 )
295 ->whereIsNotNull("give_formmeta_attach_meta_settings.meta_value")
296 ->whereIsNotNull("give_formmeta_attach_meta_fields.meta_value")
297 ->where('post_type', 'give_forms');
298 }
299
300 /**
301 * @return PaymentGateway[]
302 */
303 public function getEnabledPaymentGateways($formId): array
304 {
305 $gateways = [];
306
307 $enabledGateways = give_get_option('gateways_v3', []);
308
309 if (!empty($enabledGateways)) {
310 foreach ($enabledGateways as $gatewayId => $enabled) {
311 if (!$enabled || !$this->paymentGatewayRegister->hasPaymentGateway($gatewayId)) {
312 continue;
313 }
314
315 $gateway = $this->paymentGatewayRegister->getPaymentGateway($gatewayId);
316
317 if (!in_array(3, $gateway->supportsFormVersions(), true)) {
318 continue;
319 }
320
321 $gateways[$gatewayId] = $gateway;
322 }
323
324 $defaultGateway = give_get_default_gateway($formId, 3);
325
326 if (array_key_exists($defaultGateway, $gateways)) {
327 $gateways = array_merge([$defaultGateway => $gateways[$defaultGateway]], $gateways);
328 }
329 }
330
331 return apply_filters('givewp_donation_form_enabled_gateways', $gateways, $formId);
332 }
333
334 /**
335 * @since 3.0.0
336 */
337 public function getDefaultEnabledGatewayId(int $formId): string
338 {
339 $gateways = $this->getEnabledPaymentGateways($formId);
340
341 return !empty($gateways) ? current($gateways)::id() : '';
342 }
343
344 /**
345 * @since 3.0.0
346 */
347 public function getFormDataGateways(int $formId): array
348 {
349 $formDataGateways = [];
350
351 foreach ($this->getEnabledPaymentGateways($formId) as $gateway) {
352 $gatewayId = $gateway::id();
353 $settings = $this->getGatewayFormSettings($formId, $gateway);
354 $label = give_get_gateway_checkout_label($gatewayId, 3) ?? $gateway->getPaymentMethodLabel();
355
356 /*
357 * TODO: Make gateway arrayable
358 */
359 $formDataGateways[] = [
360 'id' => $gatewayId,
361 'label' => $label,
362 'supportsSubscriptions' => $gateway->supportsSubscriptions(),
363 'settings' => $settings,
364 ];
365 }
366
367 return $formDataGateways;
368 }
369
370 /**
371 *
372 * @since 3.0.0
373 */
374 public function getTotalNumberOfDonors(int $formId): int
375 {
376 return DB::table('give_donationmeta')
377 ->where('meta_key', DonationMetaKeys::DONOR_ID)
378 ->whereIn('donation_id', function ($builder) use ($formId) {
379 $builder
380 ->select('donation_id')
381 ->from('give_donationmeta')
382 ->where('meta_key', DonationMetaKeys::FORM_ID)
383 ->where('meta_value', $formId);
384 })->count('DISTINCT meta_value');
385 }
386
387 /**
388 *
389 * @since 3.0.0
390 */
391 public function getTotalNumberOfDonorsFromSubscriptions(int $formId): int
392 {
393 return DB::table('give_subscriptions')
394 ->where('product_id', $formId)
395 ->count('DISTINCT customer_id');
396 }
397
398 /**
399 * @since 3.0.0
400 */
401 public function getTotalNumberOfDonations(int $formId): int
402 {
403 return (new DonationQuery)
404 ->form($formId)
405 ->count();
406 }
407
408 /**
409 * @since 3.0.0
410 */
411 public function getTotalNumberOfSubscriptions(int $formId): int
412 {
413 return DB::table('give_subscriptions')
414 ->where('product_id', $formId)
415 ->count();
416 }
417
418 /**
419 * @since 3.12.0 Update query to use intended amounts (without recovered fees).
420 * @since 3.0.0
421 */
422 public function getTotalRevenue(int $formId): int
423 {
424 return (int) (new DonationQuery)
425 ->form($formId)
426 ->sumIntendedAmount();
427 }
428
429 /**
430 * @since 3.0.0
431 * @return int|float
432 */
433 public function getTotalInitialAmountFromSubscriptions(int $formId)
434 {
435 return DB::table('give_subscriptions')
436 ->where('product_id', $formId)
437 ->sum('initial_amount');
438 }
439
440 /**
441 * @since 3.0.0
442 * @throws NameCollisionException
443 */
444 public function getFormSchemaFromBlocks(int $formId, BlockCollection $blocks): DonationFormNode
445 {
446 try {
447 [$form, $blockNodeRelationships] = (new ConvertDonationFormBlocksToFieldsApi())($blocks, $formId);
448 $formNodes = $form->all();
449
450 /** @var Section $firstSection */
451 $firstSection = $form->count() ? $formNodes[0] : null;
452
453 if ($firstSection) {
454 $firstSection->append(
455 Hidden::make('formId')
456 ->defaultValue($formId)
457 ->rules(
458 'required', 'integer',
459 function ($value, Closure $fail, string $key, array $values) use ($formId) {
460 if ($value !== $formId) {
461 $fail('Invalid donation form ID');
462 }
463 }
464 )
465 );
466 }
467 } catch (NameCollisionException $exception) {
468 throw $exception;
469 } catch (Exception $exception) {
470 Log::error('Failed converting donation form blocks to fields', compact('formId', 'blocks'));
471
472 $form = new DonationFormNode('donation-form');
473 $blockNodeRelationships = [];
474 }
475
476 Hooks::doAction('givewp_donation_form_schema', $form, $formId, $blockNodeRelationships);
477
478 return $form;
479 }
480
481 /**
482 * @since 3.0.3 Use isV3Form() method instead of 'post_content' to check if it's a legacy form
483 * @since 3.0.0
484 */
485 public function isLegacyForm(int $formId): bool
486 {
487 return ! Utils::isV3Form($formId);
488 }
489
490 /**
491 * @since 4.7.0
492 */
493 public function getColorSettings(DonationForm $donationForm): array
494 {
495 $primaryColor = $donationForm->settings->primaryColor;
496 $secondaryColor = $donationForm->settings->secondaryColor;
497
498 if ($donationForm->settings->inheritCampaignColors) {
499 /** @var Campaign $campaign */
500 $campaign = give()->campaigns->getByFormId($donationForm->id);
501
502 if ($campaign) {
503 $primaryColor = $campaign->primaryColor;
504 $secondaryColor = $campaign->secondaryColor;
505 }
506 }
507
508 return [
509 'primaryColor' => $primaryColor,
510 'secondaryColor' => $secondaryColor,
511 ];
512 }
513
514 /**
515 * Get gateway form settings and handle any exceptions.
516 *
517 * @since 3.0.0
518 */
519 private function getGatewayFormSettings(int $formId, PaymentGateway $gateway): array
520 {
521 if (!method_exists($gateway, 'formSettings')) {
522 return [];
523 }
524
525 try {
526 return $gateway->formSettings($formId);
527 } catch (\Exception $exception) {
528 $gatewayName = $gateway->getName();
529 Log::error("Failed getting gateway ($gatewayName) form settings", [
530 'formId' => $formId,
531 'gateway' => $gatewayName,
532 'error' => $exception->getMessage(),
533 ]);
534
535 return [];
536 }
537 }
538 }
539