PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.7.0.1
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.7.0.1
1.8.13.1 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 All 212 releases
charitable / includes / admin / forms / class-charitable-admin-donation-form.php

class-charitable-admin-donation-form.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.7.0.1, at includes/admin/forms/class-charitable-admin-donation-form.php

770 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin donation form model class.
4 *
5 * @package Charitable/Classes/Charitable_Admin_Donation_Form
6 * @author David Bisset
7 * @copyright Copyright (c) 2022, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.5.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_Admin_Donation_Form' ) ) :
19
20 /**
21 * Charitable_Admin_Donation_Form
22 *
23 * @since 1.5.0
24 */
25 class Charitable_Admin_Donation_Form extends Charitable_Admin_Form {
26
27 /**
28 * Current Charitable_Donation object, or false if it's a new donation.
29 *
30 * @since 1.5.0
31 *
32 * @var Charitable_Donation|false
33 */
34 protected $donation;
35
36 /**
37 * Form action.
38 *
39 * @since 1.5.0
40 *
41 * @var string
42 */
43 protected $form_action;
44
45 /**
46 * Merged fields.
47 *
48 * @since 1.5.11
49 *
50 * @var array
51 */
52 protected $merged_fields;
53
54 /**
55 * Create a donation form object.
56 *
57 * @since 1.5.0
58 *
59 * @param Charitable_Donation|false $donation For existing donations, the `Charitable_Donation` instance.
60 * False for new donations.
61 */
62 public function __construct( $donation ) {
63 $this->id = uniqid();
64 $this->donation = $donation;
65 $this->form_action = $this->has_donation() ? 'update_donation' : 'add_donation';
66 }
67
68 /**
69 * Return the current Charitable_Donation instance, or false if this is a new donation.
70 *
71 * @since 1.5.0
72 *
73 * @return Charitable_Donation|false
74 */
75 public function get_donation() {
76 return $this->donation;
77 }
78
79 /**
80 * Whether there is a current active donation we are editing.
81 *
82 * @since 1.5.0
83 *
84 * @return boolean
85 */
86 public function has_donation() {
87 return $this->donation && ! in_array( $this->donation->get_status(), array( 'auto-draft', 'draft' ) );
88 }
89
90 /**
91 * Return the donation form fields.
92 *
93 * @since 1.5.0
94 *
95 * @return array[]
96 */
97 public function get_fields() {
98 $fields = array(
99 'donation_fields' => array(
100 'type' => 'fieldset',
101 'fields' => $this->get_donation_fields(),
102 'priority' => 21,
103 'tabindex' => 1,
104 ),
105 'donor_header' => array(
106 'type' => 'heading',
107 'level' => 'h3',
108 'title' => __( 'Donor', 'charitable' ),
109 'priority' => 40,
110 ),
111 'user_fields' => array(
112 'type' => 'fieldset',
113 'fields' => $this->get_user_fields(),
114 'priority' => 50,
115 'tabindex' => 100,
116 ),
117 'meta_fields' => array(
118 'type' => 'fieldset',
119 'fields' => $this->get_meta_fields(),
120 'priority' => 60,
121 'tabindex' => 200,
122 ),
123 );
124
125 if ( $this->has_donation() ) {
126 if ( 'manual' !== $this->get_donation()->get_gateway() ) {
127 $fields['meta_fields']['fields']['date']['type'] = 'hidden';
128 $fields['meta_fields']['fields']['date']['value'] = Charitable_Data_Processor::sanitize_datepicker( $fields['meta_fields']['fields']['date']['value'] );
129 }
130
131 $fields['meta_fields']['fields']['time'] = array(
132 'type' => 'hidden',
133 'priority' => 2,
134 'value' => date( 'H:i:s', strtotime( $this->get_donation()->post_date_gmt ) ),
135 );
136 } else {
137 $fields['donor_id'] = array(
138 'type' => 'select',
139 'options' => $this->get_all_donors(),
140 'priority' => 41,
141 'value' => '',
142 'description' => __( 'Select an existing donor or choose "Add a New Donor" to create a new donor.', 'charitable' ),
143 'wrapper_class' => array( 'select2' ),
144 'attrs' => array(
145 'data-nonce' => wp_create_nonce( 'donor-select' ),
146 ),
147 );
148 }
149
150 /* User can only create a donation for themselves. */
151 if ( ! current_user_can( 'edit_others_donations' ) ) {
152 $user = charitable_get_user( get_current_user_id() );
153
154 if ( array_key_exists( 'donor_id', $fields ) ) {
155 $fields['donor_id']['type'] = 'hidden';
156 $fields['donor_id']['value'] = $user->get_donor_id();
157 }
158
159 foreach ( $fields['user_fields']['fields'] as $key => $details ) {
160 $fields['user_fields']['fields'][ $key ]['value'] = $user->$key;
161
162 if ( $key === 'email' ) {
163 $fields['user_fields']['fields'][ $key ]['field_attrs'] = array(
164 'attrs' => array( 'disabled' => 'disabled' ),
165 );
166 }
167 }
168 }
169
170 /**
171 * Filter the admin donation form fields.
172 *
173 * Note that the recommended way to add fields to the form is
174 * with the Donation Fields API. This filter provides the ability
175 * to re-organize the sections within the form and change fields
176 * in the form that do not come from the Donation Fields API
177 * (headers, campaign/amount field, resend receipt).
178 *
179 * @since 1.5.0
180 *
181 * @param array $fields Array of fields.
182 * @param Charitable_Admin_Donation_Form $form This instance of `Charitable_Admin_Donation_Form`.
183 */
184 $fields = apply_filters( 'charitable_admin_donation_form_fields', $fields, $this );
185
186 uasort( $fields, 'charitable_priority_sort' );
187
188 return $fields;
189 }
190
191 /**
192 * Get donation fields.
193 *
194 * @since 1.5.0
195 *
196 * @return array
197 */
198 public function get_donation_fields() {
199 if ( ! $this->donation ) {
200 $value = array();
201 } else {
202 $value = (array) $this->donation->get_campaign_donations();
203 }
204
205 return array(
206 'campaign_donations' => array(
207 'type' => 'campaign-donations',
208 'value' => $value,
209 ),
210 );
211 }
212
213 /**
214 * Return all the fields in a particular section.
215 *
216 * @since 1.5.0
217 *
218 * @param string $section The section we're fetching fields for.
219 * @return array
220 */
221 public function get_section_fields( $section ) {
222 $fields = charitable()->donation_fields()->get_admin_form_fields( $section );
223 $keys = array_keys( $fields );
224 $fields = array_combine(
225 $keys,
226 array_map( array( $this, 'maybe_set_field_value' ), wp_list_pluck( $fields, 'admin_form' ), $keys )
227 );
228
229 uasort( $fields, 'charitable_priority_sort' );
230
231 return $fields;
232 }
233
234 /**
235 * Return the fields for the user section.
236 *
237 * @since 1.6.28
238 *
239 * @return array
240 */
241 public function get_user_fields() {
242 $fields = $this->get_section_fields( 'user' );
243 $loading_icon = charitable()->get_path( 'assets', false ) . '/images/charitable-loading.gif';
244 $fields['overlay'] = array(
245 'type' => 'content',
246 'priority' => 1,
247 'content' => '<div class="charitable-overlay" style="display: none;"><img src="' . $loading_icon . '" width=60 height=60 alt="' . __( 'Loading icon', 'charitable' ) . '" /></div>',
248 );
249
250 uasort( $fields, 'charitable_priority_sort' );
251
252 return $fields;
253 }
254
255 /**
256 * Return the fields for the meta section.
257 *
258 * @since 1.6.28
259 *
260 * @return array
261 */
262 public function get_meta_fields() {
263 $fields = $this->get_section_fields( 'meta' );
264 $fields['log_note'] = array(
265 'label' => __( 'Donation Note', 'charitable' ),
266 'type' => 'textarea',
267 'priority' => 12,
268 'required' => false,
269 );
270
271 if ( $this->should_add_donation_receipt_checkbox() ) {
272 $fields['send_donation_receipt'] = array(
273 'type' => 'checkbox',
274 'label' => __( 'Send an email receipt to the donor.', 'charitable' ),
275 'value' => 1,
276 'default' => 1,
277 'priority' => 16,
278 );
279 }
280
281 uasort( $fields, 'charitable_priority_sort' );
282
283 return $fields;
284 }
285
286 /**
287 * Get the value submitted for a particular field.
288 *
289 * @since 1.5.0
290 *
291 * @param string $field The field.
292 * @param mixed $default The default value to return if the value was not submitted.
293 * @return mixed
294 */
295 public function get_submitted_value( $field, $default = false ) {
296 $data = $this->get_submitted_values();
297 return isset( $data[ $field ] ) ? $data[ $field ] : $default;
298 }
299
300 /**
301 * Filter a campaign donation array, making sure both a campaign
302 * and amount are provided.
303 *
304 * @since 1.5.0
305 *
306 * @param object $campaign_donation A particular campaign donation.
307 * @return boolean
308 */
309 public function filter_campaign_donation( $campaign_donation ) {
310 $campaign_donation = (array) $campaign_donation;
311
312 return array_key_exists( 'campaign_id', $campaign_donation )
313 && array_key_exists( 'amount', $campaign_donation )
314 && ! empty( $campaign_donation['campaign_id'] )
315 && ! empty( $campaign_donation['amount'] );
316 }
317
318 /**
319 * Validate the form submission.
320 *
321 * @since 1.5.0
322 *
323 * @return boolean
324 */
325 public function validate_submission() {
326 /* If we have already validated the submission, return the value. */
327 if ( isset( $this->validated ) ) {
328 return $this->valid;
329 }
330
331 $this->valid = $this->check_required_fields( $this->get_merged_fields() );
332
333 /**
334 * If required fields are missing, get the error message from the
335 * notices and add them to the admin notices.
336 */
337 if ( ! $this->valid ) {
338 charitable_get_admin_notices()->fill_notices_from_frontend();
339 }
340
341 $campaign_donations = array_key_exists( 'campaign_donations', $_POST ) ? $_POST['campaign_donations'] : array();
342 $_POST['campaign_donations'] = array_filter( $campaign_donations, array( $this, 'filter_campaign_donation' ) );
343
344 if ( empty( $_POST['campaign_donations'] ) ) {
345 charitable_get_admin_notices()->add_error( __( 'You must provide both a campaign and amount.', 'charitable' ) );
346
347 $this->valid = false;
348 }
349
350 if ( $this->donation_needs_email() ) {
351 charitable_get_admin_notices()->add_error( __( 'Please choose an existing donor or provide an email address for a new donor.', 'charitable' ) );
352
353 $this->valid = false;
354 }
355
356 /**
357 * Filter whether the admin donation form passes validation.
358 *
359 * @since 1.5.0
360 *
361 * @param boolean $valid Whether the form submission is valid.
362 * @param Charitable_Admin_Donation_Form $form This instance of `Charitable_Admin_Donation_Form`.
363 */
364 $this->valid = apply_filters( 'charitable_validate_admin_donation_form_submission', $this->valid, $this );
365 $this->validated = true;
366
367 return $this->valid;
368 }
369
370 /**
371 * Return the donation values.
372 *
373 * @since 1.5.0
374 *
375 * @return array
376 */
377 public function get_donation_values() {
378 $values = array(
379 'ID' => $this->get_submitted_value( 'ID' ),
380 'donor_id' => abs( (int) $this->get_submitted_value( 'donor_id' ) ),
381 'status' => $this->get_submitted_value( 'status' ),
382 'log_note' => $this->get_submitted_value( 'log_note' ),
383 'user_id' => 0,
384 );
385
386 if ( 'add_donation' == $this->get_submitted_value( 'charitable_action' ) ) {
387 $values['donation_gateway'] = __( 'Manual', 'charitable' );
388 }
389
390 $values = $this->sanitize_submitted_campaign_donation( $values );
391 $values = $this->sanitize_submitted_date( $values );
392 $values = $this->sanitize_submitted_log_note( $values );
393 $values = $this->sanitize_submitted_donor( $values );
394
395 /* Ensure that the user has not created a donation for someone else. */
396 if ( ! current_user_can( 'edit_others_donations' ) ) {
397 $user = charitable_get_user( get_current_user_id() );
398
399 $values['donor_id'] = $user->get_donor_id();
400
401 if ( array_key_exists( 'user_id', $values ) ) {
402 $values['user_id'] = $user->ID;
403 }
404
405 if ( array_key_exists( 'user', $values ) && array_key_exists( 'email', $values['user'] ) ) {
406 $values['user']['email'] = $user->email;
407 }
408 }
409
410 foreach ( $this->get_merged_fields() as $key => $field ) {
411 if ( $this->should_field_be_added( $field, $key, $values ) ) {
412 $values[ $field['data_type'] ][ $key ] = $this->get_field_value_from_submission( $field, $key );
413 }
414 }
415
416 /**
417 * Filter the submitted values.
418 *
419 * @since 1.5.0
420 *
421 * @param array $values The submitted values.
422 * @param Charitable_Admin_Donation_Form $form This instance of `Charitable_Admin_Donation_Form`.
423 */
424 return apply_filters( 'charitable_admin_donation_form_submission_values', $values, $this );
425 }
426
427 /**
428 * Return the value for a particular field from the form submission, or return the default.
429 *
430 * @since 1.5.7
431 *
432 * @param array $field The field definition.
433 * @param string $key The key of the field.
434 * @return mixed
435 */
436 protected function get_field_value_from_submission( $field, $key ) {
437 $default = 'checkbox' == $field['type'] ? false : '';
438
439 return $this->get_submitted_value( $key, $default );
440 }
441
442 /**
443 * Checks whether a field should be added to the values to be saved.
444 *
445 * @since 1.5.7
446 *
447 * @param array $field The field definition.
448 * @param string $key The key of the field.
449 * @param array $values The sanitized values so far.
450 * @return boolean
451 */
452 protected function should_field_be_added( $field, $key, $values ) {
453 if ( ! $this->should_data_type_be_added( $field ) || ! array_key_exists( 'type', $field ) ) {
454 return false;
455 }
456
457 if ( isset( $values[ $field['data_type'] ][ $key ] ) ) {
458 return false;
459 }
460
461 return 'user' == $field['data_type'] ? $this->should_user_field_be_added( $values ) : true;
462 }
463
464 /**
465 * Whether the passed data type should be added to the values to be saved.
466 *
467 * @since 1.5.7
468 *
469 * @param array $field The field definition.
470 * @return boolean
471 */
472 protected function should_data_type_be_added( $field ) {
473 return array_key_exists( 'data_type', $field ) && 'core' != $field['data_type'];
474 }
475
476 /**
477 * Returns whether users fields should be added.
478 *
479 * This will return true if no donor_id is set, or if this is an edit
480 * of an existing donation.
481 *
482 * @since 1.5.7
483 *
484 * @param array $values The sanitized values so far.
485 * @return boolean
486 */
487 protected function should_user_field_be_added( $values ) {
488 return ! $values['donor_id'] || $values['ID'];
489 }
490
491 /**
492 * Return donor values.
493 *
494 * @since 1.5.0
495 *
496 * @param array $values The submitted values.
497 * @return array
498 */
499 protected function sanitize_submitted_donor( $values ) {
500 /* Shortcircuit for new donations. */
501 if ( ! $values['donor_id'] ) {
502 if ( $values['ID'] && $this->has_donation() ) {
503 $values['user'] = $this->get_donor_data_to_preserve();
504 }
505
506 return $values;
507 }
508
509 $donor = new Charitable_Donor( $values['donor_id'] );
510
511 /* Populate the user_id' arg, then prepare the 'user' array. */
512 $values['user_id'] = $donor->get_user()->ID;
513 $values['user'] = array(
514 'email' => $this->get_submitted_value( 'email' ),
515 'first_name' => $this->get_submitted_value( 'first_name' ),
516 'last_name' => $this->get_submitted_value( 'last_name' ),
517 'address' => $this->get_submitted_value( 'address' ),
518 'address_2' => $this->get_submitted_value( 'address_2' ),
519 'postcode' => $this->get_submitted_value( 'postcode' ),
520 'state' => $this->get_submitted_value( 'state' ),
521 'country' => $this->get_submitted_value( 'country' ),
522 'phone' => $this->get_submitted_value( 'phone' ),
523 );
524
525 return $values;
526 }
527
528 /**
529 * Returns any of the current donor data that should be preserved
530 * after saving. This is any data that exists that doesn't have a
531 * field in the admin donation form.
532 *
533 * @since 1.5.11
534 *
535 * @return array
536 */
537 protected function get_donor_data_to_preserve() {
538 $data = $this->donation->get_donor_data();
539
540 foreach ( $this->get_merged_fields() as $key => $field ) {
541 if ( ! array_key_exists( 'data_type', $field ) || 'user' != $field['data_type'] ) {
542 continue;
543 }
544
545 if ( ! array_key_exists( $key, $data ) ) {
546 continue;
547 }
548
549 unset( $data[ $key ] );
550
551 /* There is no data left to preserve, so return an empty array. */
552 if ( empty( $data ) ) {
553 return $data;
554 }
555 }
556
557 return $data;
558 }
559
560 /**
561 * Sanitize the log note, or add one if none was included.
562 *
563 * @since 1.5.0
564 *
565 * @param array $values The submitted values.
566 * @return array
567 */
568 protected function sanitize_submitted_log_note( $values ) {
569 if ( ! $values['log_note'] ) {
570 $values['log_note'] = sprintf(
571 __( 'Donation updated manually by <a href="%1$s">%2$s</a>.', 'charitable' ),
572 admin_url( 'user-edit.php?user_id=' . wp_get_current_user()->ID ),
573 wp_get_current_user()->display_name
574 );
575 } else {
576 $values['log_note'] .= sprintf(
577 ' - <a href="%s">%s</a>',
578 admin_url( 'user-edit.php?user_id=' . wp_get_current_user()->ID ),
579 wp_get_current_user()->display_name
580 );
581 }
582
583 return $values;
584 }
585
586 /**
587 * Sanitize the campaign donation submitted.
588 *
589 * @since 1.5.0
590 *
591 * @param array $values The submitted values.
592 * @return array
593 */
594 protected function sanitize_submitted_campaign_donation( $values ) {
595 $campaigns = array();
596
597 foreach ( $this->get_submitted_value( 'campaign_donations' ) as $key => $campaign_donation ) {
598 $campaign_donation['amount'] = charitable_get_currency_helper()->sanitize_monetary_amount( $campaign_donation['amount'] );
599 $campaigns[ $key ] = $campaign_donation;
600 }
601
602 $values['campaigns'] = $campaigns;
603
604 if ( $this->has_donation() ) {
605 $old_campaign_ids = wp_list_pluck( $this->donation->get_campaign_donations(), 'campaign_id' );
606
607 foreach ( $campaigns as $campaign_donation ) {
608 if ( ! in_array( $campaign_donation['campaign_id'], $old_campaign_ids ) ) {
609 Charitable_Campaign::flush_donations_cache( $campaign_donation['campaign_id'] );
610 }
611 }
612 }
613
614 return $values;
615 }
616
617 /**
618 * Sanitize the date.
619 *
620 * @since 1.5.0
621 *
622 * @param array $values The submitted values.
623 * @return array
624 */
625 protected function sanitize_submitted_date( $values ) {
626 $donation = charitable_get_donation( $this->get_submitted_value( 'ID' ) );
627 $is_new = false === $donation || 'Auto Draft' === $donation->post_title;
628 $date = $this->get_submitted_value( 'date' );
629 $time = $this->get_submitted_value( 'time', '00:00:00' );
630
631 /* The date has already been sanitized in Charitable_Date_Processor::sanitize_datepicker */
632 $values['date_gmt'] = $date . ' ' . $time;
633
634 /* If the date matches today's date and it's a new donation, save the time too. */
635 if ( date( 'Y-m-d 00:00:00' ) == $values['date_gmt'] && $is_new ) {
636 $values['date_gmt'] = date( 'Y-m-d H:i:s' );
637 }
638
639 /* If the donation date has been changed, the time is always set to 00:00:00 */
640 if ( $values['date_gmt'] !== $donation->post_date_gmt && ! $is_new ) {
641 $values['date_gmt'] = $date . ' 00:00:00';
642 }
643
644 return $values;
645 }
646
647 /**
648 * Get a key=>value array of all existing donors.
649 *
650 * @since 1.5.0
651 *
652 * @return array
653 */
654 protected function get_all_donors() {
655 $donors = new Charitable_Donor_Query(
656 array(
657 'number' => -1,
658 'orderby' => 'name',
659 'order' => 'ASC',
660 'output' => 'raw',
661 'status' => false, // Return any.
662 'include_erased' => false,
663 )
664 );
665
666 $donor_list = array();
667
668 foreach ( $donors as $donor ) {
669 $name = trim( sprintf( '%s %s', $donor->first_name, $donor->last_name ) );
670
671 if ( charitable_is_valid_email_address( $donor->email ) ) {
672 $name .= ' - ' . $donor->email;
673 }
674
675 $donor_list[ $donor->donor_id ] = $name;
676 }
677
678 $list = array(
679 'new' => __( 'Add a New Donor', 'charitable' ),
680 'existing' => array(
681 'label' => __( 'Existing Donors', 'charitable' ),
682 'options' => $donor_list,
683 ),
684 );
685
686 return $list;
687 }
688
689 /**
690 * Set a field's initial value.
691 *
692 * @since 1.5.0
693 *
694 * @param array $field Field definition.
695 * @param string $key The key of the field.
696 * @return array
697 */
698 protected function maybe_set_field_value( $field, $key ) {
699 if ( array_key_exists( $key, $_POST ) ) {
700 $field['value'] = $_POST[ $key ];
701 return $field;
702 }
703
704 /* Checkboxes don't need a value set. */
705 if ( 'checkbox' != $field['type'] ) {
706 $field['value'] = array_key_exists( 'default', $field ) ? $field['default'] : '';
707 }
708
709 if ( ! $this->has_donation() ) {
710 return $field;
711 }
712
713 if ( isset( $field['value_callback'] ) ) {
714 $value = call_user_func( $field['value_callback'], $this->get_donation(), $key );
715 } else {
716 $value = $this->donation->get( $key );
717 }
718
719 if ( isset( $field['timestamp_callback'] ) ) {
720 $field['timestamp'] = call_user_func( $field['timestamp_callback'], $this->get_donation(), $key );
721 }
722
723 if ( ! $value ) {
724 return $field;
725 }
726
727 if ( 'checkbox' == $field['type'] ) {
728 $field['checked'] = $value;
729 } else {
730 $field['value'] = $value;
731 }
732
733 return $field;
734 }
735
736 /**
737 * Returns whether a checkbox should be included for sending the donation receipt.
738 *
739 * @since 1.5.9
740 *
741 * @return boolean
742 */
743 protected function should_add_donation_receipt_checkbox() {
744 return ! $this->has_donation() && charitable_get_helper( 'emails' )->is_enabled_email( 'donation_receipt' );
745 }
746
747 /**
748 * Return whether the donation needs an email address.
749 *
750 * @since 1.6.0
751 *
752 * @return boolean
753 */
754 public function donation_needs_email() {
755 if ( charitable_permit_donor_without_email() ) {
756 return false;
757 }
758
759 if ( strlen( $this->get_submitted_value( 'email', '' ) ) ) {
760 return false;
761 }
762
763 $donor_id = $this->get_submitted_value( 'donor_id' );
764
765 return 'new' == $donor_id || '' == $donor_id;
766 }
767 }
768
769 endif;
770