PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13.1
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13.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
← All changes | includes/admin/forms/class-charitable-admin-donation-form.php +184 -89 1.6.6 → 1.8.13.1 View file →
@@ -2,17 +2,19 @@
2 2 /**
3 3 * Admin donation form model class.
4 4 *
5 5 * @package Charitable/Classes/Charitable_Admin_Donation_Form
6 - * @author Eric Daams
7 - * @copyright Copyright (c) 2018, Studio 164a
6 + * @author David Bisset
7 + * @copyright Copyright (c) 2023, WP Charitable LLC
8 8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 9 * @since 1.5.0
10 - * @version 1.5.11
10 + * @version 1.6.55
11 11 */
12 12
13 13 // Exit if accessed directly.
14 -if ( ! defined( 'ABSPATH' ) ) { exit; }
14 +if ( ! defined( 'ABSPATH' ) ) {
15 + exit;
16 +}
15 17
16 18 if ( ! class_exists( 'Charitable_Admin_Donation_Form' ) ) :
17 19
18 20 /**
@@ -107,15 +109,15 @@
107 109 'priority' => 40,
108 110 ),
109 111 'user_fields' => array(
110 112 'type' => 'fieldset',
111 - 'fields' => $this->get_section_fields( 'user' ),
113 + 'fields' => $this->get_user_fields(),
112 114 'priority' => 50,
113 115 'tabindex' => 100,
114 116 ),
115 117 'meta_fields' => array(
116 118 'type' => 'fieldset',
117 - 'fields' => $this->get_section_fields( 'meta' ),
119 + 'fields' => $this->get_meta_fields(),
118 120 'priority' => 60,
119 121 'tabindex' => 200,
120 122 ),
121 123 );
@@ -120,30 +122,50 @@
120 122 ),
121 123 );
122 124
123 125 if ( $this->has_donation() ) {
124 - if ( 'manual' != $this->get_donation()->get_gateway() ) {
125 - $fields['meta_fields']['fields']['date']['type'] = 'hidden';
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'] );
126 129 }
127 130
128 131 $fields['meta_fields']['fields']['time'] = array(
129 132 'type' => 'hidden',
130 133 'priority' => 2,
131 - 'value' => date( 'H:i:s', strtotime( $this->get_donation()->post_date_gmt ) ),
134 + 'value' => date( 'H:i:s', strtotime( $this->get_donation()->post_date_gmt ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
132 135 );
133 136 } else {
134 137 $fields['donor_id'] = array(
135 - 'type' => 'select',
136 - 'options' => $this->get_all_donors(),
137 - 'priority' => 41,
138 - 'value' => '',
139 - 'description' => __( 'Select an existing donor or choose "Add a New Donor" to create a new donor.', 'charitable' ),
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 + ),
140 147 );
148 + }
141 149
142 - $fields['user_fields']['attrs'] = array(
143 - 'data-trigger-key' => '#donor-id',
144 - 'data-trigger-value' => 'new',
145 - );
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 + }
146 168 }
147 169
148 170 /**
149 171 * Filter the admin donation form fields.
@@ -155,10 +177,10 @@
155 177 * (headers, campaign/amount field, resend receipt).
156 178 *
157 179 * @since 1.5.0
158 180 *
159 - * @var array $fields Array of fields.
160 - * @var Charitable_Admin_Donation_Form $form This instance of `Charitable_Admin_Donation_Form`.
181 + * @param array $fields Array of fields.
182 + * @param Charitable_Admin_Donation_Form $form This instance of `Charitable_Admin_Donation_Form`.
161 183 */
162 184 $fields = apply_filters( 'charitable_admin_donation_form_fields', $fields, $this );
163 185
164 186 uasort( $fields, 'charitable_priority_sort' );
@@ -203,27 +225,29 @@
203 225 $keys,
204 226 array_map( array( $this, 'maybe_set_field_value' ), wp_list_pluck( $fields, 'admin_form' ), $keys )
205 227 );
206 228
207 - if ( 'meta' == $section ) {
208 - $fields['log_note'] = array(
209 - 'label' => __( 'Donation Note', 'charitable' ),
210 - 'type' => 'textarea',
211 - 'priority' => 12,
212 - 'required' => false,
213 - );
229 + uasort( $fields, 'charitable_priority_sort' );
214 230
215 - if ( $this->should_add_donation_receipt_checkbox() ) {
216 - $fields['send_donation_receipt'] = array(
217 - 'type' => 'checkbox',
218 - 'label' => __( 'Send an email receipt to the donor.', 'charitable' ),
219 - 'value' => 1,
220 - 'default' => 1,
221 - 'priority' => 16,
222 - );
223 - }
224 - }
231 + return $fields;
232 + }
225 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 +
226 250 uasort( $fields, 'charitable_priority_sort' );
227 251
228 252 return $fields;
229 253 }
@@ -228,28 +252,37 @@
228 252 return $fields;
229 253 }
230 254
231 255 /**
232 - * Return the merged fields.
256 + * Return the fields for the meta section.
233 257 *
234 - * @since 1.5.0
258 + * @since 1.6.28
259 + * @since 1.8.1.8 Added the `charitable_admin_donation_form_meta_fields` filter.
235 260 *
236 261 * @return array
237 262 */
238 - public function get_merged_fields() {
239 - if ( ! isset( $this->merged_fields ) ) {
240 - $this->merged_fields = array();
263 + public function get_meta_fields() {
264 + $fields = $this->get_section_fields( 'meta' );
265 + $fields['log_note'] = array(
266 + 'label' => __( 'Donation Note', 'charitable' ),
267 + 'type' => 'textarea',
268 + 'priority' => 12,
269 + 'required' => false,
270 + );
241 271
242 - foreach ( $this->get_fields() as $section_id => $section ) {
243 - if ( array_key_exists( 'fields', $section ) ) {
244 - $this->merged_fields = array_merge( $this->merged_fields, $section['fields'] );
245 - } else {
246 - $this->merged_fields[ $section_id ] = $section;
247 - }
248 - }
272 + if ( $this->should_add_donation_receipt_checkbox() ) {
273 + $fields['send_donation_receipt'] = array(
274 + 'type' => 'checkbox',
275 + 'label' => __( 'Send an email receipt to the donor.', 'charitable' ),
276 + 'value' => 1,
277 + 'default' => 1,
278 + 'priority' => 16,
279 + );
249 280 }
250 281
251 - return $this->merged_fields;
282 + uasort( $fields, 'charitable_priority_sort' );
283 +
284 + return apply_filters( 'charitable_admin_donation_form_meta_fields', $fields, $this );
252 285 }
253 286
254 287 /**
255 288 * Get the value submitted for a particular field.
@@ -260,9 +293,10 @@
260 293 * @param mixed $default The default value to return if the value was not submitted.
261 294 * @return mixed
262 295 */
263 296 public function get_submitted_value( $field, $default = false ) {
264 - return array_key_exists( $field, $_POST ) ? $_POST[ $field ] : $default;
297 + $data = $this->get_submitted_values();
298 + return isset( $data[ $field ] ) ? $data[ $field ] : $default;
265 299 }
266 300
267 301 /**
268 302 * Filter a campaign donation array, making sure both a campaign
@@ -269,11 +303,14 @@
269 303 * and amount are provided.
270 304 *
271 305 * @since 1.5.0
272 306 *
307 + * @param object $campaign_donation A particular campaign donation.
273 308 * @return boolean
274 309 */
275 310 public function filter_campaign_donation( $campaign_donation ) {
311 + $campaign_donation = (array) $campaign_donation;
312 +
276 313 return array_key_exists( 'campaign_id', $campaign_donation )
277 314 && array_key_exists( 'amount', $campaign_donation )
278 315 && ! empty( $campaign_donation['campaign_id'] )
279 316 && ! empty( $campaign_donation['amount'] );
@@ -293,12 +330,24 @@
293 330 }
294 331
295 332 $this->valid = $this->check_required_fields( $this->get_merged_fields() );
296 333
297 - $campaign_donations = array_key_exists( 'campaign_donations', $_POST ) ? $_POST['campaign_donations'] : array();
298 - $_POST['campaign_donations'] = array_filter( $campaign_donations, array( $this, 'filter_campaign_donation' ) );
334 + /**
335 + * If required fields are missing, get the error message from the
336 + * notices and add them to the admin notices.
337 + */
338 + if ( ! $this->valid ) {
339 + charitable_get_admin_notices()->fill_notices_from_frontend();
340 + }
299 341
300 - if ( empty( $_POST['campaign_donations'] ) ) {
342 + // phpcs:disable WordPress.Security.NonceVerification.Missing
343 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Array data, sanitized in filter_campaign_donation
344 + $campaign_donations = array_key_exists( 'campaign_donations', $_POST ) ? wp_unslash( $_POST['campaign_donations'] ) : array();
345 + $campaign_donations = array_filter( $campaign_donations, array( $this, 'filter_campaign_donation' ) );
346 + $_POST['campaign_donations'] = $campaign_donations;
347 +
348 + if ( empty( $campaign_donations ) ) {
349 + // phpcs:enable WordPress.Security.NonceVerification.Missing
301 350 charitable_get_admin_notices()->add_error( __( 'You must provide both a campaign and amount.', 'charitable' ) );
302 351
303 352 $this->valid = false;
304 353 }
@@ -332,9 +381,9 @@
332 381 */
333 382 public function get_donation_values() {
334 383 $values = array(
335 384 'ID' => $this->get_submitted_value( 'ID' ),
336 - 'donor_id' => abs( $this->get_submitted_value( 'donor_id' ) ),
385 + 'donor_id' => abs( (int) $this->get_submitted_value( 'donor_id' ) ),
337 386 'status' => $this->get_submitted_value( 'status' ),
338 387 'log_note' => $this->get_submitted_value( 'log_note' ),
339 388 'user_id' => 0,
340 389 );
@@ -347,8 +396,23 @@
347 396 $values = $this->sanitize_submitted_date( $values );
348 397 $values = $this->sanitize_submitted_log_note( $values );
349 398 $values = $this->sanitize_submitted_donor( $values );
350 399
400 + /* Ensure that the user has not created a donation for someone else. */
401 + if ( ! current_user_can( 'edit_others_donations' ) ) {
402 + $user = charitable_get_user( get_current_user_id() );
403 +
404 + $values['donor_id'] = $user->get_donor_id();
405 +
406 + if ( array_key_exists( 'user_id', $values ) ) {
407 + $values['user_id'] = $user->ID;
408 + }
409 +
410 + if ( array_key_exists( 'user', $values ) && array_key_exists( 'email', $values['user'] ) ) {
411 + $values['user']['email'] = $user->email;
412 + }
413 + }
414 +
351 415 foreach ( $this->get_merged_fields() as $key => $field ) {
352 416 if ( $this->should_field_be_added( $field, $key, $values ) ) {
353 417 $values[ $field['data_type'] ][ $key ] = $this->get_field_value_from_submission( $field, $key );
354 418 }
@@ -448,20 +512,20 @@
448 512 }
449 513
450 514 $donor = new Charitable_Donor( $values['donor_id'] );
451 515
452 - /* Populate the 'user' and 'user_id' args with this donor's stored details. */
516 + /* Populate the user_id' arg, then prepare the 'user' array. */
453 517 $values['user_id'] = $donor->get_user()->ID;
454 518 $values['user'] = array(
455 - 'email' => $donor->get_donor_meta( 'email' ),
456 - 'first_name' => $donor->get_donor_meta( 'first_name' ),
457 - 'last_name' => $donor->get_donor_meta( 'last_name' ),
458 - 'address' => $donor->get_donor_meta( 'address' ),
459 - 'address_2' => $donor->get_donor_meta( 'address_2' ),
460 - 'postcode' => $donor->get_donor_meta( 'postcode' ),
461 - 'state' => $donor->get_donor_meta( 'state' ),
462 - 'country' => $donor->get_donor_meta( 'country' ),
463 - 'phone' => $donor->get_donor_meta( 'phone' ),
519 + 'email' => $this->get_submitted_value( 'email' ),
520 + 'first_name' => $this->get_submitted_value( 'first_name' ),
521 + 'last_name' => $this->get_submitted_value( 'last_name' ),
522 + 'address' => $this->get_submitted_value( 'address' ),
523 + 'address_2' => $this->get_submitted_value( 'address_2' ),
524 + 'postcode' => $this->get_submitted_value( 'postcode' ),
525 + 'state' => $this->get_submitted_value( 'state' ),
526 + 'country' => $this->get_submitted_value( 'country' ),
527 + 'phone' => $this->get_submitted_value( 'phone' ),
464 528 );
465 529
466 530 return $values;
467 531 }
@@ -507,14 +571,17 @@
507 571 * @return array
508 572 */
509 573 protected function sanitize_submitted_log_note( $values ) {
510 574 if ( ! $values['log_note'] ) {
511 - $values['log_note'] = sprintf( __( 'Donation updated manually by <a href="%s">%s</a>.', 'charitable' ),
575 + $values['log_note'] = sprintf(
576 + /* translators: %1$s: User profile URL, %2$s: User display name */
577 + __( 'Donation updated manually by <a href="%1$s">%2$s</a>.', 'charitable' ),
512 578 admin_url( 'user-edit.php?user_id=' . wp_get_current_user()->ID ),
513 579 wp_get_current_user()->display_name
514 580 );
515 581 } else {
516 - $values['log_note'] .= sprintf( ' - <a href="%s">%s</a>',
582 + $values['log_note'] .= sprintf(
583 + ' - <a href="%s">%s</a>',
517 584 admin_url( 'user-edit.php?user_id=' . wp_get_current_user()->ID ),
518 585 wp_get_current_user()->display_name
519 586 );
520 587 }
@@ -555,28 +622,44 @@
555 622
556 623 /**
557 624 * Sanitize the date.
558 625 *
559 - * @since 1.5.0
626 + * @since 1.5.0
627 + * @version 1.8.8.5
560 628 *
561 629 * @param array $values The submitted values.
562 630 * @return array
563 631 */
564 632 protected function sanitize_submitted_date( $values ) {
565 - $donation = charitable_get_donation( $this->get_submitted_value( 'ID' ) );
566 - $is_new = false === $donation || 'Auto Draft' === $donation->post_title;
567 - $date = $this->get_submitted_value( 'date' );
568 - $time = $this->get_submitted_value( 'time', '00:00:00' );
569 - $values['date_gmt'] = charitable_sanitize_date( $date, 'Y-m-d ' . $time );
633 + $donation = charitable_get_donation( $this->get_submitted_value( 'ID' ) );
634 + $is_new = false === $donation || 'Auto Draft' === $donation->post_title;
635 + $date = $this->get_submitted_value( 'date' );
636 + $time = $this->get_submitted_value( 'time', '00:00:00' );
570 637
638 + /* The date has already been sanitized in Charitable_Date_Processor::sanitize_datepicker */
639 + $values['date_gmt'] = $date . ' ' . $time;
640 +
571 641 /* If the date matches today's date and it's a new donation, save the time too. */
572 - if ( date( 'Y-m-d 00:00:00' ) == $values['date_gmt'] && $is_new ) {
573 - $values['date_gmt'] = date( 'Y-m-d H:i:s' );
642 + if ( date( 'Y-m-d 00:00:00' ) == $values['date_gmt'] && $is_new ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
643 + $values['date_gmt'] = date( 'Y-m-d H:i:s' ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
574 644 }
575 645
576 - /* If the donation date has been changed, the time is always set to 00:00:00 */
646 + /* If the donation date has been changed, preserve the original time from the donation log */
577 647 if ( $values['date_gmt'] !== $donation->post_date_gmt && ! $is_new ) {
578 - $values['date_gmt'] = charitable_sanitize_date( $date, 'Y-m-d 00:00:00' );
648 + // Get the original donation time from the donation log
649 + $donation_log = get_post_meta( $donation->ID, '_donation_log', true );
650 + $original_time = '00:00:00'; // Default fallback
651 +
652 + if ( is_array( $donation_log ) && ! empty( $donation_log ) ) {
653 + // Get the first log entry which contains the original creation time
654 + $first_log_entry = $donation_log[0];
655 + if ( isset( $first_log_entry['time'] ) ) {
656 + // Convert the original timestamp to GMT time format
657 + $original_time = gmdate( 'H:i:s', $first_log_entry['time'] );
658 + }
659 + }
660 +
661 + $values['date_gmt'] = $date . ' ' . $original_time;
579 662 }
580 663
581 664 return $values;
582 665 }
@@ -583,21 +666,24 @@
583 666
584 667 /**
585 668 * Get a key=>value array of all existing donors.
586 669 *
587 - * @since 1.5.0
670 + * @since 1.5.0
671 + * @version 1.8.8
588 672 *
589 673 * @return array
590 674 */
591 675 protected function get_all_donors() {
592 - $donors = new Charitable_Donor_Query( array(
593 - 'number' => -1,
594 - 'orderby' => 'name',
595 - 'order' => 'ASC',
596 - 'output' => 'raw',
597 - 'status' => false, // Return any.
598 - 'include_erased' => false,
599 - ) );
676 + $donors = new Charitable_Donor_Query(
677 + array(
678 + 'number' => -1,
679 + 'orderby' => 'name',
680 + 'order' => 'ASC',
681 + 'output' => 'raw',
682 + 'status' => false, // Return any.
683 + 'include_erased' => false,
684 + )
685 + );
600 686
601 687 $donor_list = array();
602 688
603 689 foreach ( $donors as $donor ) {
@@ -609,10 +695,12 @@
609 695
610 696 $donor_list[ $donor->donor_id ] = $name;
611 697 }
612 698
699 + // Apply a filter here to allow for the donor list to be modified.
700 + $donor_list = apply_filters( 'charitable_admin_donation_form_donor_list', $donor_list );
701 +
613 702 $list = array(
614 - '' => __( 'Select a Donor', 'charitable' ),
615 703 'new' => __( 'Add a New Donor', 'charitable' ),
616 704 'existing' => array(
617 705 'label' => __( 'Existing Donors', 'charitable' ),
618 706 'options' => $donor_list,
@@ -625,8 +713,9 @@
625 713 /**
626 714 * Set a field's initial value.
627 715 *
628 716 * @since 1.5.0
717 + * @version 1.8.9.1
629 718 *
630 719 * @param array $field Field definition.
631 720 * @param string $key The key of the field.
632 721 * @return array
@@ -631,10 +720,12 @@
631 720 * @param string $key The key of the field.
632 721 * @return array
633 722 */
634 723 protected function maybe_set_field_value( $field, $key ) {
724 + // phpcs:disable WordPress.Security.NonceVerification.Missing
635 725 if ( array_key_exists( $key, $_POST ) ) {
636 - $field['value'] = $_POST[ $key ];
726 + $field['value'] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
727 + // phpcs:enable WordPress.Security.NonceVerification.Missing
637 728 return $field;
638 729 }
639 730
640 731 /* Checkboxes don't need a value set. */
@@ -645,12 +736,16 @@
645 736 if ( ! $this->has_donation() ) {
646 737 return $field;
647 738 }
648 739
649 - if ( array_key_exists( 'value_callback', $field ) ) {
740 + if ( isset( $field['value_callback'] ) ) {
650 741 $value = call_user_func( $field['value_callback'], $this->get_donation(), $key );
651 742 } else {
652 743 $value = $this->donation->get( $key );
744 + }
745 +
746 + if ( isset( $field['timestamp_callback'] ) ) {
747 + $field['timestamp'] = call_user_func( $field['timestamp_callback'], $this->get_donation(), $key );
653 748 }
654 749
655 750 if ( ! $value ) {
656 751 return $field;