PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.11.3
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.11.3
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / includes / donations / class-charitable-donation-processor.php

class-charitable-donation-processor.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.11.3, at includes/donations/class-charitable-donation-processor.php

1,090 lines 32.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The class that is responsible for responding to donation events.
4 *
5 * @package Charitable/Classes/Charitable_Donation_Processor
6 * @author David Bisset
7 * @copyright Copyright (c) 2023, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 * @version 1.6.55
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Donation_Processor' ) ) :
19
20 /**
21 * Charitable Donation Processor.
22 *
23 * @since 1.0.0
24 */
25 class Charitable_Donation_Processor {
26
27 /**
28 * The single instance of this class.
29 *
30 * @var Charitable_Donation_Processor|null
31 */
32 private static $instance = null;
33
34 /**
35 * The campaign receiving a donation.
36 *
37 * @var Charitable_Campaign|false
38 */
39 protected $campaign;
40
41 /**
42 * The donation data.
43 *
44 * @var mixed[]
45 */
46 protected $donation_data;
47
48 /**
49 * The campaign donations array.
50 *
51 * @var array
52 */
53 protected $campaign_donations_data;
54
55 /**
56 * The donor ID for the current donation.
57 *
58 * @var int
59 */
60 protected $donor_id;
61
62 /**
63 * The donoration ID for the current donation.
64 *
65 * @var int
66 */
67 protected $donation_id;
68
69 /**
70 * Create class object. A protected constructor, so this is used in a singleton context.
71 *
72 * @since 1.0.0
73 */
74 protected function __construct() {
75 $this->campaign = charitable_get_current_campaign();
76 }
77
78 /**
79 * Returns and/or create the single instance of this class.
80 *
81 * @since 1.0.0
82 *
83 * @return Charitable_Donation_Processor
84 */
85 public static function get_instance() {
86 if ( is_null( self::$instance ) ) {
87 self::$instance = new self();
88 }
89
90 return self::$instance;
91 }
92
93 /**
94 * Destroy the Charitable_Donation_Processor instance.
95 *
96 * This is primarily useful for testing purposes, as it allows you to
97 * create multiple donations in a single request.
98 *
99 * @since 1.3.0
100 *
101 * @return void
102 */
103 public static function destroy() {
104 self::$instance = null;
105 }
106
107 /**
108 * Return the current campaign.
109 *
110 * @since 1.0.0
111 *
112 * @return Charitable_Campaign|false False if no campaign is set. Campaign object otherwise.
113 */
114 public function get_campaign() {
115 return $this->campaign;
116 }
117
118 /**
119 * Return the donation ID.
120 *
121 * @since 1.4.0
122 *
123 * @return int
124 */
125 public function get_donation_id() {
126 return $this->donation_id;
127 }
128
129 /**
130 * Executed when a user first clicks the Donate button on a campaign.
131 *
132 * @since 1.0.0
133 * @version 1.8.3.2
134 *
135 * @return void
136 */
137 public static function add_donation_to_session() {
138 $processor = self::get_instance();
139
140 if ( ! $processor->get_campaign() ) {
141 return;
142 }
143
144 if ( ! isset( $_POST['charitable-donate-now'] ) ) {
145 return;
146 }
147
148 $nonce = sanitize_text_field( wp_unslash( $_POST['charitable-donate-now'] ) );
149
150 if ( ! wp_verify_nonce( $nonce, 'charitable-donate' ) ) {
151
152 /* For backwards compatibility, we also check this nonce. */
153 if ( ! wp_verify_nonce( $nonce, 'charitable-donate-' ) ) {
154 return;
155 }
156 }
157
158 /* Possibly add a donation amount if user clicked on a campaign builder donate button */
159 $donation_amount = ! empty( $_POST['charitable_donation_amount'] ) && ! empty( $_POST['charitable_builder'] ) ? esc_html( $_POST['charitable_donation_amount'] ) : 0; // phpcs:ignore
160
161 /* Strip any currency symbols and make sure $donation_amount is a float. */
162 $donation_amount = charitable_sanitize_amount( (string) $donation_amount );
163
164 /* Save the donation in the session */
165 charitable_get_session()->add_donation( $processor->get_campaign()->ID, $donation_amount );
166
167 $donations_url = charitable_get_permalink( 'campaign_donation_page', array( 'campaign_id' => $processor->get_campaign()->ID ) );
168
169 wp_safe_redirect( $donations_url );
170 exit();
171 }
172
173 /**
174 * Process the donation.
175 *
176 * This is used by all donation form submissions, AJAX or not.
177 *
178 * @since 1.3.0
179 *
180 * @return mixed
181 */
182 public function process_donation() {
183 $processor = self::get_instance();
184 $campaign = $processor->get_campaign();
185
186 if ( charitable_is_debug() ) {
187 error_log( 'process_donation start' ); // phpcs:ignore
188 }
189
190 if ( ! $campaign ) {
191 return;
192 }
193
194 /* Check to ensure the donation is not going to an inactive campaign post. */
195 if ( ! $campaign->can_receive_donations() ) {
196 if ( charitable_is_debug() ) {
197 error_log( 'Campaign cannot receive donations' ); // phpcs:ignore
198 }
199 return;
200 }
201
202 /* Validate the form submission and retrieve the values. */
203 $form = $campaign->get_donation_form();
204 if ( charitable_is_debug() ) {
205 // phpcs:disable
206 error_log( 'process_donation - form' );
207 error_log( print_r( $form, true ) );
208 // phpcs:enable
209 }
210
211 if ( charitable_is_debug() ) {
212 // phpcs:disable
213 error_log( '=== FORM DATA DEBUG ===' );
214 error_log( 'POST data available to form: ' . print_r( $_POST, true ) );
215 error_log( 'REQUEST data available to form: ' . print_r( $_REQUEST, true ) );
216 // phpcs:enable
217 }
218
219 /**
220 * Do something before any donation form processing takes place.
221 *
222 * @since 1.0.0
223 *
224 * @param Charitable_Donation_Processor $processor The Donation Processor object.
225 * @param Charitable_Donation_Form $form The Donation Form object.
226 */
227 do_action( 'charitable_before_process_donation_form', $processor, $form );
228
229 if ( ! $form->validate_submission() ) {
230 if ( charitable_is_debug() ) {
231 // phpcs:disable
232 error_log( 'Validation form not valid' );
233 // phpcs:enable
234 }
235 return false;
236 }
237
238 $values = $form->get_donation_values();
239 $gateway = $values['gateway'];
240
241 if ( charitable_is_debug() ) {
242 // phpcs:disable
243 error_log( '=== DONATION PROCESSOR DEBUG ===' );
244 error_log( 'Gateway from form values: ' . $gateway );
245 error_log( 'All form values: ' . print_r( $values, true ) );
246 error_log( 'POST data: ' . print_r( $_POST, true ) );
247 // phpcs:enable
248 }
249
250 /**
251 * Validate the gateway values.
252 *
253 * @since 1.0.0
254 *
255 * @param boolean $valid Whether the submission passes validation.
256 * @param string $gateway The gateway ID.
257 * @param array $values The values submitted by the user.
258 */
259 if ( ! apply_filters( 'charitable_validate_donation_form_submission_gateway', true, $gateway, $values ) ) {
260 if ( charitable_is_debug() ) {
261 // phpcs:disable
262 error_log( 'process_donation not valid' );
263 error_log( print_r( $gateway, true ) );
264 error_log( print_r( $values, true ) );
265 // phpcs:enable
266 }
267 return false;
268 }
269
270 $this->donation_id = $processor->save_donation( $values );
271
272 /**
273 * Set a transient to allow plugins to act on this donation on the next page load.
274 */
275 set_transient( 'charitable_donation_' . charitable_get_session()->get_session_id(), $this );
276
277 /**
278 * We check whether the gateway is compatible with version 1.3, since Charitable 1.3
279 * change the hook into a filter (instead of an action).
280 */
281 if ( $this->gateway_is_130_compatible( $gateway ) ) {
282
283 /**
284 * Pass donation processing to the payment gateway and return the results.
285 *
286 * Clients should return one of three results:
287 *
288 * - TRUE : If the donation was processed successfully and the user should
289 * be redirected to the donation receipt.
290 * - FALSE : If the donation could not be processed successfully and the user
291 * should be redirected back to the donation form.
292 * - ARRAY : If the user needs to be redirected somewhere other than the donation
293 * receipt or donation form. Array should contain two values:
294 * `redirect` : The url to be redirected to.
295 * `safe` : Whether to use wp_safe_redirect. If unset, will default to true.
296 *
297 * @since 1.3.0
298 *
299 * @param mixed $result The result of gateway processing.
300 * @param int $donation_id The donation ID.
301 * @param Charitable_Donation_Processor $processor The Donation Processor object.
302 * @return mixed
303 */
304 if ( charitable_is_debug() ) {
305 // phpcs:disable
306 error_log( 'This Gateway is Charitable version 1.3.0 compatible... The gateway is ' . $gateway );
307 error_log( 'The donation id is ' . $this->donation_id );
308 error_log( 'Charitable_Donation_Processor Object is: [commented out] ' );
309 // phpcs:enable
310 }
311
312 $hook_name = 'charitable_process_donation_' . $gateway;
313
314 return apply_filters( $hook_name, true, $this->donation_id, $processor ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Hook name is dynamically constructed with the plugin prefix 'charitable_process_donation_'.
315
316 } else {
317 /**
318 * Send donation processing to the payment gateway.
319 *
320 * This is a fallback for payment gateways that have not been updated to the filter method above.
321 *
322 * @since 1.0.0
323 *
324 * @param int $donation_id The donation ID.
325 * @param Charitable_Donation_Processor $processor The Donation Processor object.
326 */
327 do_action( 'charitable_process_donation_' . $gateway, $this->donation_id, $processor );
328
329 /**
330 * If we get this far, forward the user through to the receipt page.
331 */
332 wp_safe_redirect( charitable_get_permalink( 'donation_receipt_page', array( 'donation_id' => $this->donation_id ) ) );
333
334 die();
335
336 }//end if
337 }
338
339 /**
340 * Save a donation submitted through a page reload (i.e. not AJAX).
341 *
342 * @since 1.0.0
343 *
344 * @return void
345 */
346 public static function process_donation_form_submission() {
347 $processor = self::get_instance();
348 $result = $processor->process_donation();
349
350 $processor->redirect_after_gateway_processing( $result );
351 }
352
353 /**
354 * Add a donation with AJAX.
355 *
356 * @since 1.3.0
357 * @version 1.8.7
358 *
359 * @return void
360 */
361 public static function ajax_process_donation_form_submission() {
362 /* Prevent any displayed errors from messing with the AJAX response. */
363 if ( charitable_is_debug() ) { // phpcs:ignore
364 // do nothing, allow php/server settings to determine php logging.
365 } else {
366 ini_set( 'display_errors', 0 ); // phpcs:ignore
367 }
368
369 if ( ! isset( $_POST['campaign_id'] ) ) { // @codingStandardsIgnoreLine
370 wp_send_json_error( new WP_Error( 'missing_campaign_id', __( 'Campaign ID was not found. Unable to create donation.', 'charitable' ) ) );
371 }
372
373 $processor = self::get_instance();
374 $result = $processor->process_donation();
375
376 $response = is_array( $result ) ? $result : array();
377
378 if ( $processor->donation_processed_successfully( $result ) ) {
379 $response['success'] = true;
380 $response['redirect_to'] = $processor->get_redirection_after_gateway_processing( $result );
381 } else {
382
383 // Start with getting errors from the notices class.
384 $errors = charitable_get_notices()->get_errors();
385 // If there is no errors, set the errors to an empty array.
386 if ( empty( $errors ) || ! is_array( $errors ) ) {
387 $errors = array();
388 }
389
390 // Check and see if there are errors in the response too.
391 if ( ! empty( $response['errors'] ) ) {
392 $errors = array_merge( $errors, $response['errors'] );
393 }
394
395 if ( empty( $errors ) ) {
396 $errors = array( __( 'Unable to process donation.', 'charitable' ) );
397 }
398
399 // Log the AJAX processing failure
400 $context = array(
401 'campaign_id' => $processor->get_donation_data_value( 'campaign_id' ),
402 'amount' => $processor->get_donation_data_value( 'donation_amount' ),
403 'gateway' => $processor->get_donation_data_value( 'gateway' ),
404 'donation_id' => (int) $processor->get_donation_id(),
405 );
406 charitable_log_form_error( 'ajax_failure', 'donation_processing_failed', $context );
407
408 $response['success'] = false;
409 $response['errors'] = $errors;
410 $response['donation_id'] = (int) $processor->get_donation_id();
411
412 // get the gateway from the processor.
413 $gateway = $processor->get_donation_data_value( 'gateway' );
414
415 // If the gateway is square, we need to add to add errors to the donation log.
416 if ( 'square' === $gateway ) {
417 // If we are in test mode, remove the first error because that reads "Test mode is enabled".
418 if ( charitable_get_option( 'test_mode' ) ) {
419 // If there is at least one element, check the first one for a value that contains "Test mode is enabled".
420 if ( count( $errors ) > 0 ) {
421 if ( strpos( $errors[0], 'Test mode is enabled' ) !== false ) {
422 $errors = array_slice( $errors, 1 );
423 }
424 }
425 }
426 $errors = implode( ', ', $errors );
427 $processor->update_donation_log( $processor->get_donation_id(), 'Payment attempt failed.' );
428 }
429 }
430
431 if ( charitable_is_debug() ) {
432 // phpcs:disable
433 error_log( 'ajax_process_donation_form_submission - response' );
434 error_log( print_r( $response, true ) );
435 // phpcs:enable
436 }
437
438 // Experimenting with a different response structure.
439 // These are errors displayed to the user (and hopefully not loggeD).
440 if ( ! empty( $response['errors'] ) ) {
441 // Insert an element into the first position of the array.
442 $response['errors'] = array_merge( array( 'An error occurred while processing your donation.' ), $response['errors'] );
443 }
444 wp_send_json( $response );
445
446 exit();
447 }
448
449 /**
450 * Save a donation.
451 *
452 * @since 1.0.0
453 *
454 * @return void
455 */
456 public static function make_donation_streamlined() {
457 $processor = self::get_instance();
458 $campaign = $processor->get_campaign();
459
460 if ( ! $campaign ) {
461 return;
462 }
463
464 /* Validate the form submission and retrieve the values. */
465 $form = new Charitable_Donation_Amount_Form( $campaign );
466
467 /**
468 * Do something before the donation amount form is processed.
469 *
470 * @hook charitable_before_process_donation_amount_form
471 */
472 do_action( 'charitable_before_process_donation_amount_form', $processor, $form );
473
474 /**
475 * Form validated, so add the donation to session.
476 */
477 if ( $form->validate_submission() ) {
478 $submitted = $form->get_donation_values();
479 $period = array_key_exists( 'donation_period', $submitted ) ? $submitted['donation_period'] : 'once';
480
481 charitable_get_session()->add_donation( $submitted['campaign_id'], $submitted['amount'], $period );
482
483 /**
484 * Do something after the donation amount form is processed.
485 *
486 * @hook charitable_after_process_donation_amount_form
487 */
488 do_action( 'charitable_after_process_donation_amount_form', $processor, $submitted );
489 }
490
491 $redirect_url = charitable_get_permalink( 'campaign_donation_page', array( 'campaign_id' => $campaign->ID ) );
492
493 if ( 'same_page' == charitable_get_option( 'donation_form_display', 'separate_page' ) ) { // phpcs:ignore
494 $redirect_url .= '#charitable-donation-form';
495 }
496
497 wp_safe_redirect( $redirect_url );
498 die();
499 }
500
501 /**
502 * Checks whether a donation has been processed successfully.
503 *
504 * @since 1.6.44
505 *
506 * @param boolean|array $result The result returned from process_donation.
507 * @return boolean
508 */
509 public function donation_processed_successfully( $result ) {
510 if ( is_array( $result ) ) {
511 return array_key_exists( 'success', $result ) ? $result['success'] : true;
512 }
513
514 return $result;
515 }
516
517 /**
518 * Inserts a new donation.
519 *
520 * This method is designed to be completely form agnostic.
521 *
522 * We use this when integrating third-party systems like Easy Digital Downloads and
523 * WooCommerce.
524 *
525 * @since 1.0.0
526 *
527 * @param mixed[] $values Submitted donation values.
528 * @return int $donation_id Returns 0 in case of failure. Positive donation ID otherwise.
529 */
530 public function save_donation( array $values ) {
531 /**
532 * Filter the donation values to be saved.
533 *
534 * @since 1.0.0
535 *
536 * @param array $values The donation values to be saved.
537 */
538 $this->donation_data = apply_filters( 'charitable_donation_values', $values );
539
540 if ( ! $this->get_campaign_donations_data() ) {
541 charitable_get_deprecated()->doing_it_wrong(
542 __METHOD__,
543 __( 'A donation cannot be inserted without an array of campaigns being donated to.', 'charitable' ),
544 '1.0.0'
545 );
546 return 0;
547 }
548
549 if ( ! $this->is_valid_user_data() ) {
550 charitable_get_deprecated()->doing_it_wrong(
551 __METHOD__,
552 __( 'A donation cannot be inserted without valid user data.', 'charitable' ),
553 '1.0.0'
554 );
555 return 0;
556 }
557
558 /**
559 * Do something right before the donation is inserted into the database.
560 *
561 * @since 1.0.0
562 *
563 * @param Charitable_Donation_Processor $this The Processor instance.
564 */
565 do_action( 'charitable_before_save_donation', $this );
566
567 $donation_id = wp_insert_post( $this->parse_donation_data() );
568
569 $this->set_donation_key();
570
571 if ( is_wp_error( $donation_id ) ) {
572 charitable_get_notices()->add_errors_from_wp_error( $donation_id );
573 return 0;
574 }
575
576 if ( 0 == $donation_id ) { // phpcs:ignore
577 charitable_get_notices()->add_error( __( 'We were unable to save the donation. Please try again.', 'charitable' ) );
578 return 0;
579 }
580
581 $this->save_campaign_donations( $donation_id );
582
583 $this->save_donation_meta( $donation_id );
584
585 $this->save_donor_contact_consent();
586
587 $log_note = array_key_exists( 'log_note', $values ) ? $values['log_note'] : '';
588
589 if ( empty( $log_note ) ) {
590 $log_note = isset( $values['ID'] ) && 0 !== $values['ID'] ? __( 'Payment attempted.', 'charitable' ) : __( 'Donation created.', 'charitable' );
591 }
592
593 $this->update_donation_log( $donation_id, $log_note );
594
595 /**
596 * Update the user session if we're on the public site or in an AJAX request.
597 *
598 * 1. Write the donation key to the session.
599 * 2. Write the campaign donation amounts to the session. This is required in
600 * case the donor gets sent back to the donation form (cancellation, failure).
601 */
602 if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
603
604 $session = charitable_get_session();
605
606 /* Required for the donation receipt to load for logged out donors. */
607 $session->add_donation_key( $this->get_donation_data_value( 'donation_key' ) );
608
609 /* Required in case the donor is redirected back to the donation form. */
610 foreach ( $this->get_campaign_donations_data() as $campaign ) {
611 $session->add_donation( $campaign['campaign_id'], $campaign['amount'], $this->get_donation_data_value( 'donation_key', 'once' ) );
612 }
613 }
614
615 /**
616 * Do something after the donation has been saved in the database.
617 *
618 * @since 1.0.0
619 *
620 * @param int $donation_id The donation ID.
621 * @param Charitable_Donation_Processor $this The Processor instance.
622 */
623 do_action( 'charitable_after_save_donation', $donation_id, $this );
624
625 return $donation_id;
626 }
627
628 /**
629 * Inserts the campaign donations into the campaign_donations table.
630 *
631 * @since 1.0.0
632 *
633 * @param int $donation_id The donation ID.
634 * @return int The number of donations inserted.
635 */
636 public function save_campaign_donations( $donation_id ) {
637 $donor_id = $this->get_donor_id();
638 $campaigns = $this->get_campaign_donations_data();
639
640 if ( false !== $this->get_donation_data_value( 'ID', false ) ) {
641 $records = charitable_get_table( 'campaign_donations' )->get_donation_records( $donation_id );
642 $campaign_donations = wp_list_pluck( $records, 'campaign_id', 'campaign_donation_id' );
643 } else {
644 $campaign_donations = array();
645 }
646
647 foreach ( $campaigns as $campaign ) {
648
649 if ( array_key_exists( 'campaign_donation_id', $campaign ) ) {
650 $campaign_donation_id = $campaign['campaign_donation_id'];
651 } else {
652 $campaign_donation_id = array_search( $campaign['campaign_id'], $campaign_donations ); // phpcs:ignore
653 }
654
655 /* Avoid adding duplicate campaign donations when re-submitting a campaign. */
656 if ( false !== $campaign_donation_id ) {
657
658 /* Cast the existing campaign_donation record to an array and sanitize the amount. */
659 $campaign_donation = (array) $records[ $campaign_donation_id ];
660 $campaign_donation['amount'] = charitable_sanitize_amount( $campaign_donation['amount'], true );
661
662 /* Get our updated campaign donation values. */
663 $args = charitable_array_subset( $campaign, array( 'campaign_id', 'campaign_name', 'amount' ) );
664 $args['donor_id'] = $donor_id;
665
666 /* If any values have changed, update the record. */
667 if ( count( array_diff( $args, $campaign_donation ) ) ) {
668 charitable_get_table( 'campaign_donations' )->update( $campaign_donation_id, $args, 'campaign_donation_id' );
669 }
670 } else {
671
672 /* Add a new donation */
673 $campaign['donor_id'] = $donor_id;
674 $campaign['donation_id'] = $donation_id;
675 $campaign_donation_id = charitable_get_table( 'campaign_donations' )->insert( $campaign );
676
677 if ( 0 == $campaign_donation_id ) { // phpcs:ignore
678 return 0;
679 }
680 }
681 }//end foreach
682
683 return count( $campaigns );
684 }
685
686 /**
687 * Save the meta for the donation.
688 *
689 * @since 1.0.0
690 *
691 * @param int $donation_id The donation ID.
692 * @return void
693 */
694 public function save_donation_meta( $donation_id ) {
695 $meta = array(
696 'donation_gateway' => $this->get_donation_data_value( 'gateway' ),
697 'donor' => $this->get_donation_data_value( 'user' ),
698 'test_mode' => charitable_get_option( 'test_mode', 0 ),
699 'donation_key' => $this->get_donation_data_value( 'donation_key' ),
700 'currency' => charitable_get_currency(),
701 );
702
703 if ( $this->get_donation_data_value( 'meta' ) ) {
704 $meta = array_merge( $meta, $this->get_donation_data_value( 'meta' ) );
705 }
706
707 /**
708 * Filter the donation meta to be saved.
709 *
710 * @since 1.0.0
711 *
712 * @param array $meta The meta to be saved, in a key=>value array.
713 * @param int $donation_id The donation ID.
714 * @param Charitable_Donation_Processor $processor This instance of `Charitable_Donation_Processor`.
715 */
716 $meta = apply_filters( 'charitable_donation_meta', $meta, $donation_id, $this );
717
718 foreach ( $meta as $meta_key => $value ) {
719 /**
720 * Sanitize a particular meta value.
721 *
722 * @since 1.0.0
723 *
724 * @param mixed $value The value.
725 * @param string $meta_key The meta key.
726 */
727 $value = apply_filters( 'charitable_sanitize_donation_meta', $value, $meta_key );
728 update_post_meta( $donation_id, $meta_key, $value );
729 }
730 }
731
732 /**
733 * Save whether the donor gave consent to be contacted.
734 *
735 * @since 1.6.0
736 *
737 * @return void
738 */
739 public function save_donor_contact_consent() {
740 /* Contact consent requires he database upgrade to have been completed. */
741 if ( ! charitable_is_contact_consent_activated() ) {
742 return;
743 }
744
745 $donor_id = $this->get_donor_id();
746 $meta = $this->get_donation_data_value( 'meta', array() );
747 $contact_consent = array_key_exists( 'contact_consent', $meta ) ? (bool) $meta['contact_consent'] : false;
748
749 charitable_get_table( 'donors' )->update(
750 $donor_id,
751 array(
752 'contact_consent' => $contact_consent,
753 )
754 );
755 }
756
757 /**
758 * Add a message to the donation log.
759 *
760 * @since 1.0.0
761 *
762 * @param int $donation_id The donation ID.
763 * @param string $message The message to add to the log.
764 * @return void
765 */
766 public function update_donation_log( $donation_id, $message ) {
767 $donation = charitable_get_donation( $donation_id );
768 if ( $donation ) {
769 $donation->log()->add( $message );
770 }
771 }
772
773 /**
774 * Returns the submitted donation data.
775 *
776 * @since 1.0.0
777 *
778 * @return mixed[]
779 */
780 public function get_donation_data() {
781 return $this->donation_data;
782 }
783
784 /**
785 * Return the submitted value for a particular key.
786 *
787 * @since 1.0.0
788 *
789 * @param string $key The key to search for.
790 * @param mixed $default Fallback value to return if the data is not set.
791 * @return mixed
792 */
793 public function get_donation_data_value( $key, $default = false ) { // phpcs:ignore
794 $data = $this->get_donation_data();
795 return isset( $data[ $key ] ) ? $data[ $key ] : $default;
796 }
797
798 /**
799 * Set a value for a particular key.
800 *
801 * @since 1.4.0
802 *
803 * @param string $key The key to set.
804 * @param mixed $value The value to be set.
805 * @return void
806 */
807 public function set_donation_data_value( $key, $value ) {
808 $this->donation_data[ $key ] = $value;
809 }
810
811 /**
812 * Returns the campaign donations array, or false if the data is invalid.
813 *
814 * @since 1.0.0
815 *
816 * @return array[]|false
817 */
818 public function get_campaign_donations_data() {
819 if ( ! isset( $this->campaign_donations_data ) ) {
820
821 if ( ! is_array( $this->get_donation_data_value( 'campaigns' ) ) ) {
822 return false;
823 }
824
825 $this->campaign_donations_data = array();
826
827 foreach ( $this->get_donation_data_value( 'campaigns' ) as $campaign ) {
828
829 /* If the amount or campaign_id are missing, this donation won't work. */
830 if ( ! isset( $campaign['campaign_id'] ) || ! isset( $campaign['amount'] ) ) {
831 return false;
832 }
833
834 if ( ! isset( $campaign['campaign_name'] ) ) {
835 $campaign['campaign_name'] = get_post_field( 'post_title', $campaign['campaign_id'], 'raw' );
836 }
837
838 $this->campaign_donations_data[] = $campaign;
839 }
840 }//end if
841
842 return $this->campaign_donations_data;
843 }
844
845 /**
846 * Returns the donor_id for the current donation.
847 *
848 * @since 1.0.0
849 *
850 * @return int
851 */
852 public function get_donor_id() {
853 if ( ! isset( $this->donor_id ) ) {
854 $this->donor_id = $this->get_donation_data_value( 'donor_id', 0 );
855
856 if ( $this->donor_id ) {
857 return $this->donor_id;
858 }
859
860 $user_data = $this->get_donation_data_value( 'user', array() );
861
862 if ( array_key_exists( 'email', $user_data ) ) {
863 $this->donor_id = charitable_get_table( 'donors' )->get_donor_id_by_email( $user_data['email'] );
864 }
865
866 /* If we still do not have a donor ID, it means that this is a first-time donor */
867 if ( 0 == $this->donor_id ) { // phpcs:ignore
868 $user = new Charitable_User( $this->get_donation_data_value( 'user_id', get_current_user_id() ) );
869 $this->donor_id = $user->add_donor( $user_data );
870 }
871 }//end if
872
873 return $this->donor_id;
874 }
875
876 /**
877 * Returns the name of the donor.
878 *
879 * @since 1.0.0
880 * @since 1.6.14 Changed access to public. Previously protected.
881 *
882 * @return string
883 */
884 public function get_donor_name() {
885 $user = new WP_User( $this->get_donation_data_value( 'user_id', 0 ) );
886 $user_data = $this->get_donation_data_value( 'user' );
887 $first_name = isset( $user_data['first_name'] ) ? $user_data['first_name'] : $user->get( 'first_name' );
888 $last_name = isset( $user_data['last_name'] ) ? $user_data['last_name'] : $user->get( 'last_name' );
889 return trim( sprintf( '%s %s', $first_name, $last_name ) );
890 }
891
892 /**
893 * Returns a comma separated list of the campaigns that are being donated to.
894 *
895 * @since 1.0.0
896 * @since 1.6.14 Changed access to public. Previously protected.
897 *
898 * @return string
899 */
900 public function get_campaign_names() {
901 $campaigns = wp_list_pluck( $this->get_campaign_donations_data(), 'campaign_name' );
902 return implode( ', ', $campaigns );
903 }
904
905 /**
906 * Returns the donation status. Defaults to charitable-pending.
907 *
908 * @since 1.0.0
909 *
910 * @return string
911 */
912 protected function get_donation_status() {
913 $status = $this->get_donation_data_value( 'status', 'charitable-pending' );
914
915 if ( ! charitable_is_valid_donation_status( $status ) ) {
916 $status = 'charitable-pending';
917 }
918
919 return $status;
920 }
921
922 /**
923 * Redirect the user after the gateway has processed the donation.
924 *
925 * @uses Charitable_Donation_Processor::get_redirection_after_gateway_processing()
926 *
927 * @since 1.3.0
928 *
929 * @param mixed $gateway_processing The result of the gateway processing.
930 * @return void
931 */
932 private function redirect_after_gateway_processing( $gateway_processing ) {
933 $redirect_url = $this->get_redirection_after_gateway_processing( $gateway_processing );
934
935 /* If the gateway processing failed, add the error notices to the session. */
936 if ( false == $gateway_processing ) { // phpcs:ignore
937
938 /* Log the failed payment. */
939 $this->update_donation_log(
940 $this->donation_id,
941 sprintf(
942 /* translators: %s: error message */
943 __( 'Payment failed with errors: %s', 'charitable' ),
944 PHP_EOL . implode( PHP_EOL, charitable_get_notices()->get_errors() )
945 )
946 );
947
948 charitable_get_session()->add_notices();
949 }
950
951 /* Set the redirect status to use. */
952 $status = isset( $gateway_processing['status'] ) ? $gateway_processing['status'] : 302;
953
954 /**
955 * If the gateway processing returned an array with a directive to NOT
956 * use wp_safe_redirect, use wp_redirect instead.
957 */
958 if ( isset( $gateway_processing['safe'] ) && false == $gateway_processing['safe'] ) { // phpcs:ignore
959 wp_safe_redirect( $redirect_url, $status );
960 die();
961 }
962
963 wp_safe_redirect( $redirect_url, $status );
964
965 die();
966 }
967
968 /**
969 * Return the URL that the donor should be redirected to.
970 *
971 * @since 1.3.0
972 *
973 * @param mixed $gateway_processing The result of the gateway processing.
974 * @return string
975 */
976 private function get_redirection_after_gateway_processing( $gateway_processing ) {
977 if ( false == $gateway_processing ) { // phpcs:ignore
978 $redirect_url = esc_url( add_query_arg( array( 'donation_id' => $this->donation_id ), wp_get_referer() ) );
979 } elseif ( is_array( $gateway_processing ) && isset( $gateway_processing['redirect'] ) ) {
980 $redirect_url = $gateway_processing['redirect'];
981 } else {
982 /* Fall back to returning the donation receipt URL. */
983 $redirect_url = charitable_get_permalink( 'donation_receipt_page', array( 'donation_id' => $this->donation_id ) );
984 }
985
986 return apply_filters( 'charitable_redirection_after_gateway_processing', $redirect_url, $gateway_processing, $this );
987 }
988
989 /**
990 * Validate user data passed to insert.
991 *
992 * @since 1.0.0
993 *
994 * @return boolean
995 */
996 protected function is_valid_user_data() {
997 if ( charitable_permit_donor_without_email() ) {
998 return true;
999 }
1000
1001 $ret = $this->get_donation_data_value( 'user_id' ) || $this->get_donation_data_value( 'donor_id' );
1002
1003 if ( ! $ret ) {
1004 $user = $this->get_donation_data_value( 'user', array() );
1005 $ret = array_key_exists( 'email', $user ) && is_email( $user['email'] );
1006 }
1007
1008 return $ret;
1009 }
1010
1011 /**
1012 * Parse the donation data, based on the passed $values array.
1013 *
1014 * @since 1.0.0
1015 *
1016 * @return array
1017 */
1018 protected function parse_donation_data() {
1019 $core_values = array(
1020 'ID' => $this->get_donation_data_value( 'ID', 0 ),
1021 'post_type' => Charitable::DONATION_POST_TYPE,
1022 'post_author' => $this->get_donation_data_value( 'user_id', get_current_user_id() ),
1023 'post_status' => $this->get_donation_status(),
1024 'post_content' => $this->get_donation_data_value( 'note', '' ),
1025 'post_parent' => $this->get_donation_data_value( 'donation_plan', 0 ),
1026 'post_date_gmt' => $this->get_donation_data_value( 'date_gmt', current_time( 'mysql', true ) ),
1027 'post_title' => sprintf( '%s &ndash; %s', $this->get_donor_name(), $this->get_campaign_names() ),
1028 );
1029
1030 $core_values['post_date'] = get_date_from_gmt( $core_values['post_date_gmt'] );
1031
1032 /**
1033 * Filter the core Donation post values to store.
1034 *
1035 * @since 1.0.0
1036 *
1037 * @param array $core_values The values to save to the posts table.
1038 * @param Charitable_Donation_Processor $processor The donation processer object.
1039 */
1040 return apply_filters( 'charitable_donation_values_core', $core_values, $this );
1041 }
1042
1043 /**
1044 * Set a unique key for the donation.
1045 *
1046 * @since 1.0.0
1047 *
1048 * @return void
1049 */
1050 protected function set_donation_key() {
1051 if ( array_key_exists( 'donation_key', $this->donation_data ) ) {
1052 return;
1053 }
1054
1055 $this->donation_data['donation_key'] = strtolower( md5( uniqid() ) );
1056 }
1057
1058 /**
1059 * Checks whether the given gateway has been updated for compatibility with 1.3.
1060 *
1061 * @since 1.3.0
1062 *
1063 * @param string $gateway The gateway for the donation.
1064 * @return boolean
1065 */
1066 private function gateway_is_130_compatible( $gateway ) {
1067 return Charitable_Gateways::get_instance()->get_gateway_object( $gateway )->supports( '1.3.0' );
1068 }
1069
1070 /**
1071 * Return the IPN url.
1072 *
1073 * IPNs in Charitable are structured in this way: charitable-listener=gateway
1074 *
1075 * @deprecated
1076 *
1077 * @since 1.0.0
1078 *
1079 * @param string $gateway The gateway for the donation.
1080 * @return string
1081 */
1082 public function get_ipn_url( $gateway ) {
1083 charitable_get_deprecated()->deprecated_function( __METHOD__, '1.4.0', 'charitable_get_ipn_url()' );
1084
1085 return charitable_get_ipn_url( $gateway );
1086 }
1087 }
1088
1089 endif;
1090