PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
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 +178 -86 1.6.9 → 1.8.13 View file →
@@ -2,13 +2,13 @@
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) 2019, 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 14 if ( ! defined( 'ABSPATH' ) ) {
@@ -109,15 +109,15 @@
109 109 'priority' => 40,
110 110 ),
111 111 'user_fields' => array(
112 112 'type' => 'fieldset',
113 - 'fields' => $this->get_section_fields( 'user' ),
113 + 'fields' => $this->get_user_fields(),
114 114 'priority' => 50,
115 115 'tabindex' => 100,
116 116 ),
117 117 'meta_fields' => array(
118 118 'type' => 'fieldset',
119 - 'fields' => $this->get_section_fields( 'meta' ),
119 + 'fields' => $this->get_meta_fields(),
120 120 'priority' => 60,
121 121 'tabindex' => 200,
122 122 ),
123 123 );
@@ -122,30 +122,50 @@
122 122 ),
123 123 );
124 124
125 125 if ( $this->has_donation() ) {
126 - if ( 'manual' != $this->get_donation()->get_gateway() ) {
127 - $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'] );
128 129 }
129 130
130 131 $fields['meta_fields']['fields']['time'] = array(
131 132 'type' => 'hidden',
132 133 'priority' => 2,
133 - '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
134 135 );
135 136 } else {
136 137 $fields['donor_id'] = array(
137 - 'type' => 'select',
138 - 'options' => $this->get_all_donors(),
139 - 'priority' => 41,
140 - 'value' => '',
141 - '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 + ),
142 147 );
148 + }
143 149
144 - $fields['user_fields']['attrs'] = array(
145 - 'data-trigger-key' => '#donor-id',
146 - 'data-trigger-value' => 'new',
147 - );
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 + }
148 168 }
149 169
150 170 /**
151 171 * Filter the admin donation form fields.
@@ -205,27 +225,29 @@
205 225 $keys,
206 226 array_map( array( $this, 'maybe_set_field_value' ), wp_list_pluck( $fields, 'admin_form' ), $keys )
207 227 );
208 228
209 - if ( 'meta' == $section ) {
210 - $fields['log_note'] = array(
211 - 'label' => __( 'Donation Note', 'charitable' ),
212 - 'type' => 'textarea',
213 - 'priority' => 12,
214 - 'required' => false,
215 - );
229 + uasort( $fields, 'charitable_priority_sort' );
216 230
217 - if ( $this->should_add_donation_receipt_checkbox() ) {
218 - $fields['send_donation_receipt'] = array(
219 - 'type' => 'checkbox',
220 - 'label' => __( 'Send an email receipt to the donor.', 'charitable' ),
221 - 'value' => 1,
222 - 'default' => 1,
223 - 'priority' => 16,
224 - );
225 - }
226 - }
231 + return $fields;
232 + }
227 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 +
228 250 uasort( $fields, 'charitable_priority_sort' );
229 251
230 252 return $fields;
231 253 }
@@ -230,28 +252,37 @@
230 252 return $fields;
231 253 }
232 254
233 255 /**
234 - * Return the merged fields.
256 + * Return the fields for the meta section.
235 257 *
236 - * @since 1.5.0
258 + * @since 1.6.28
259 + * @since 1.8.1.8 Added the `charitable_admin_donation_form_meta_fields` filter.
237 260 *
238 261 * @return array
239 262 */
240 - public function get_merged_fields() {
241 - if ( ! isset( $this->merged_fields ) ) {
242 - $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 + );
243 271
244 - foreach ( $this->get_fields() as $section_id => $section ) {
245 - if ( array_key_exists( 'fields', $section ) ) {
246 - $this->merged_fields = array_merge( $this->merged_fields, $section['fields'] );
247 - } else {
248 - $this->merged_fields[ $section_id ] = $section;
249 - }
250 - }
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 + );
251 280 }
252 281
253 - return $this->merged_fields;
282 + uasort( $fields, 'charitable_priority_sort' );
283 +
284 + return apply_filters( 'charitable_admin_donation_form_meta_fields', $fields, $this );
254 285 }
255 286
256 287 /**
257 288 * Get the value submitted for a particular field.
@@ -262,9 +293,10 @@
262 293 * @param mixed $default The default value to return if the value was not submitted.
263 294 * @return mixed
264 295 */
265 296 public function get_submitted_value( $field, $default = false ) {
266 - return array_key_exists( $field, $_POST ) ? $_POST[ $field ] : $default;
297 + $data = $this->get_submitted_values();
298 + return isset( $data[ $field ] ) ? $data[ $field ] : $default;
267 299 }
268 300
269 301 /**
270 302 * Filter a campaign donation array, making sure both a campaign
@@ -275,8 +307,10 @@
275 307 * @param object $campaign_donation A particular campaign donation.
276 308 * @return boolean
277 309 */
278 310 public function filter_campaign_donation( $campaign_donation ) {
311 + $campaign_donation = (array) $campaign_donation;
312 +
279 313 return array_key_exists( 'campaign_id', $campaign_donation )
280 314 && array_key_exists( 'amount', $campaign_donation )
281 315 && ! empty( $campaign_donation['campaign_id'] )
282 316 && ! empty( $campaign_donation['amount'] );
@@ -296,12 +330,24 @@
296 330 }
297 331
298 332 $this->valid = $this->check_required_fields( $this->get_merged_fields() );
299 333
300 - $campaign_donations = array_key_exists( 'campaign_donations', $_POST ) ? $_POST['campaign_donations'] : array();
301 - $_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 + }
302 341
303 - 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
304 350 charitable_get_admin_notices()->add_error( __( 'You must provide both a campaign and amount.', 'charitable' ) );
305 351
306 352 $this->valid = false;
307 353 }
@@ -335,9 +381,9 @@
335 381 */
336 382 public function get_donation_values() {
337 383 $values = array(
338 384 'ID' => $this->get_submitted_value( 'ID' ),
339 - 'donor_id' => abs( $this->get_submitted_value( 'donor_id' ) ),
385 + 'donor_id' => abs( (int) $this->get_submitted_value( 'donor_id' ) ),
340 386 'status' => $this->get_submitted_value( 'status' ),
341 387 'log_note' => $this->get_submitted_value( 'log_note' ),
342 388 'user_id' => 0,
343 389 );
@@ -350,8 +396,23 @@
350 396 $values = $this->sanitize_submitted_date( $values );
351 397 $values = $this->sanitize_submitted_log_note( $values );
352 398 $values = $this->sanitize_submitted_donor( $values );
353 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 +
354 415 foreach ( $this->get_merged_fields() as $key => $field ) {
355 416 if ( $this->should_field_be_added( $field, $key, $values ) ) {
356 417 $values[ $field['data_type'] ][ $key ] = $this->get_field_value_from_submission( $field, $key );
357 418 }
@@ -451,20 +512,20 @@
451 512 }
452 513
453 514 $donor = new Charitable_Donor( $values['donor_id'] );
454 515
455 - /* Populate the 'user' and 'user_id' args with this donor's stored details. */
516 + /* Populate the user_id' arg, then prepare the 'user' array. */
456 517 $values['user_id'] = $donor->get_user()->ID;
457 518 $values['user'] = array(
458 - 'email' => $donor->get_donor_meta( 'email' ),
459 - 'first_name' => $donor->get_donor_meta( 'first_name' ),
460 - 'last_name' => $donor->get_donor_meta( 'last_name' ),
461 - 'address' => $donor->get_donor_meta( 'address' ),
462 - 'address_2' => $donor->get_donor_meta( 'address_2' ),
463 - 'postcode' => $donor->get_donor_meta( 'postcode' ),
464 - 'state' => $donor->get_donor_meta( 'state' ),
465 - 'country' => $donor->get_donor_meta( 'country' ),
466 - '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' ),
467 528 );
468 529
469 530 return $values;
470 531 }
@@ -510,14 +571,17 @@
510 571 * @return array
511 572 */
512 573 protected function sanitize_submitted_log_note( $values ) {
513 574 if ( ! $values['log_note'] ) {
514 - $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' ),
515 578 admin_url( 'user-edit.php?user_id=' . wp_get_current_user()->ID ),
516 579 wp_get_current_user()->display_name
517 580 );
518 581 } else {
519 - $values['log_note'] .= sprintf( ' - <a href="%s">%s</a>',
582 + $values['log_note'] .= sprintf(
583 + ' - <a href="%s">%s</a>',
520 584 admin_url( 'user-edit.php?user_id=' . wp_get_current_user()->ID ),
521 585 wp_get_current_user()->display_name
522 586 );
523 587 }
@@ -558,28 +622,44 @@
558 622
559 623 /**
560 624 * Sanitize the date.
561 625 *
562 - * @since 1.5.0
626 + * @since 1.5.0
627 + * @version 1.8.8.5
563 628 *
564 629 * @param array $values The submitted values.
565 630 * @return array
566 631 */
567 632 protected function sanitize_submitted_date( $values ) {
568 - $donation = charitable_get_donation( $this->get_submitted_value( 'ID' ) );
569 - $is_new = false === $donation || 'Auto Draft' === $donation->post_title;
570 - $date = $this->get_submitted_value( 'date' );
571 - $time = $this->get_submitted_value( 'time', '00:00:00' );
572 - $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' );
573 637
638 + /* The date has already been sanitized in Charitable_Date_Processor::sanitize_datepicker */
639 + $values['date_gmt'] = $date . ' ' . $time;
640 +
574 641 /* If the date matches today's date and it's a new donation, save the time too. */
575 - if ( date( 'Y-m-d 00:00:00' ) == $values['date_gmt'] && $is_new ) {
576 - $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
577 644 }
578 645
579 - /* 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 */
580 647 if ( $values['date_gmt'] !== $donation->post_date_gmt && ! $is_new ) {
581 - $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;
582 662 }
583 663
584 664 return $values;
585 665 }
@@ -586,21 +666,24 @@
586 666
587 667 /**
588 668 * Get a key=>value array of all existing donors.
589 669 *
590 - * @since 1.5.0
670 + * @since 1.5.0
671 + * @version 1.8.8
591 672 *
592 673 * @return array
593 674 */
594 675 protected function get_all_donors() {
595 - $donors = new Charitable_Donor_Query( array(
596 - 'number' => -1,
597 - 'orderby' => 'name',
598 - 'order' => 'ASC',
599 - 'output' => 'raw',
600 - 'status' => false, // Return any.
601 - 'include_erased' => false,
602 - ) );
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 + );
603 686
604 687 $donor_list = array();
605 688
606 689 foreach ( $donors as $donor ) {
@@ -612,10 +695,12 @@
612 695
613 696 $donor_list[ $donor->donor_id ] = $name;
614 697 }
615 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 +
616 702 $list = array(
617 - '' => __( 'Select a Donor', 'charitable' ),
618 703 'new' => __( 'Add a New Donor', 'charitable' ),
619 704 'existing' => array(
620 705 'label' => __( 'Existing Donors', 'charitable' ),
621 706 'options' => $donor_list,
@@ -628,8 +713,9 @@
628 713 /**
629 714 * Set a field's initial value.
630 715 *
631 716 * @since 1.5.0
717 + * @version 1.8.9.1
632 718 *
633 719 * @param array $field Field definition.
634 720 * @param string $key The key of the field.
635 721 * @return array
@@ -634,10 +720,12 @@
634 720 * @param string $key The key of the field.
635 721 * @return array
636 722 */
637 723 protected function maybe_set_field_value( $field, $key ) {
724 + // phpcs:disable WordPress.Security.NonceVerification.Missing
638 725 if ( array_key_exists( $key, $_POST ) ) {
639 - $field['value'] = $_POST[ $key ];
726 + $field['value'] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
727 + // phpcs:enable WordPress.Security.NonceVerification.Missing
640 728 return $field;
641 729 }
642 730
643 731 /* Checkboxes don't need a value set. */
@@ -648,12 +736,16 @@
648 736 if ( ! $this->has_donation() ) {
649 737 return $field;
650 738 }
651 739
652 - if ( array_key_exists( 'value_callback', $field ) ) {
740 + if ( isset( $field['value_callback'] ) ) {
653 741 $value = call_user_func( $field['value_callback'], $this->get_donation(), $key );
654 742 } else {
655 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 );
656 748 }
657 749
658 750 if ( ! $value ) {
659 751 return $field;