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.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 / campaigns / class-charitable-campaign-processor.php

class-charitable-campaign-processor.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/campaigns/class-charitable-campaign-processor.php

754 lines 18.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class that is responsible for creating campaigns.
4 *
5 * @package Charitable/Classes/Charitable_Campaign_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.5.9
10 * @version 1.6.54
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Campaign_Processor' ) ) :
19
20 /**
21 * Charitable_Campaign_Processor
22 *
23 * @since 1.5.9
24 */
25 class Charitable_Campaign_Processor {
26
27 /**
28 * Raw input args.
29 *
30 * @since 1.5.9
31 *
32 * @var array
33 */
34 protected $input;
35
36 /**
37 * Campaign values.
38 *
39 * @since 1.5.9
40 *
41 * @var array
42 */
43 protected $args;
44
45 /**
46 * Core post keys.
47 *
48 * @since 1.5.9
49 *
50 * @var array
51 */
52 protected $core_keys;
53
54 /**
55 * Campaign meta keys.
56 *
57 * @since 1.5.9
58 *
59 * @var array
60 */
61 protected $meta_keys;
62
63 /**
64 * Campaign id.
65 *
66 * @since 1.5.9
67 *
68 * @var array
69 */
70 public $campaign_id;
71
72 /**
73 * Create class object.
74 *
75 * @since 1.5.9
76 *
77 * @param array $args Values for the campaign.
78 * @param array $meta_keys Meta keys to be saved as campaign meta.
79 */
80 public function __construct( array $args = array(), $meta_keys = null ) {
81 $this->input = $args;
82 $this->core_keys = array(
83 'ID' => 'ID',
84 'post_title' => 'title',
85 'post_content' => 'content',
86 'post_author' => 'creator',
87 'post_status' => 'status',
88 'post_parent' => 'parent',
89 );
90
91 $this->meta_keys = is_array( $meta_keys ) ? $meta_keys : array(
92 'goal',
93 'end_date',
94 'suggested_donations',
95 'suggested_donations_default',
96 'allow_custom_donations',
97 'description',
98 'minimum_donation_amount',
99 );
100
101 $this->parse_args();
102 }
103
104 /**
105 * Return the query argument value for the given key.
106 *
107 * @since 1.5.9
108 *
109 * @param string $key The key of the argument.
110 * @param mixed $fallback Default value to fall back to.
111 * @return mixed|false Returns fallback if the argument is not found.
112 */
113 public function get( $key, $fallback = false ) {
114 return isset( $this->args[ $key ] ) ? $this->args[ $key ] : $fallback;
115 }
116
117 /**
118 * Set the query argument for the given key.
119 *
120 * @since 1.5.9
121 *
122 * @param string $key Key of argument to set.
123 * @param mixed $value Value to be set.
124 * @return void
125 */
126 public function set( $key, $value ) {
127 $this->input[ $key ] = $value;
128 $this->args[ $key ] = $this->sanitize( $key, $value );
129 }
130
131 /**
132 * Sanitize arguments.
133 *
134 * @since 1.5.9
135 *
136 * @param string $key Key of argument to set.
137 * @param mixed $value Value to be set.
138 * @return mixed
139 */
140 public function sanitize( $key, $value ) {
141 $key = $this->truncated_key( $key );
142
143 /**
144 * If a class sanitization method exists for this key, call it.
145 */
146 if ( $this->use_sanitizer( $key ) ) {
147 $sanitizer_method = 'sanitize_' . $key;
148 $value = $this->$sanitizer_method( $value );
149 }
150
151 /**
152 * Filter a campaign argument when it is set.
153 *
154 * @since 1.5.9
155 *
156 * @param mixed $value The value.
157 * @param Charitable_Campaign_Processor $processor This processor object.
158 */
159 return apply_filters( 'charitable_campaign_processor_sanitize_' . $key, $value, $this );
160 }
161
162 /**
163 * Save the campaign to the database.
164 *
165 * @since 1.5.9
166 *
167 * @return int The campaign ID, or 0 if the campaign was not created.
168 */
169 public function save() {
170
171 $this->campaign_id = $this->save_core();
172
173 if ( ! $this->campaign_id || is_wp_error( $this->campaign_id ) ) {
174 return 0;
175 }
176
177 $this->save_meta();
178 $this->save_taxonomies();
179
180 return $this->campaign_id;
181 }
182
183 /**
184 * Save the core data.
185 *
186 * @since 1.5.9
187 * @since 1.8.1 Added two action hooks.
188 *
189 * @return int|WP_Error The donation ID if succesful. The value 0 or WP_Error on failure.
190 */
191 public function save_core() {
192 $data = array_reduce( $this->core_keys, array( $this, 'get_core_data_value' ) );
193 $data['post_type'] = Charitable::CAMPAIGN_POST_TYPE;
194
195 /**
196 * Filter the core campaign data.
197 *
198 * @since 1.5.9
199 *
200 * @param array $data Core data.
201 * @param Charitable_Campaign_Processor $processor This processor object.
202 */
203 $data = apply_filters( 'charitable_campaign_processor_core_data', $data, $this );
204
205 /**
206 * Update or insert the campaign.
207 */
208 if ( $data['ID'] ) {
209
210 // The returned value can be either the campaign_id or 0 on fail.
211 $campaign_id = wp_update_post( $data );
212
213 /**
214 * Peform an action after the campaign has been updated.
215 *
216 * @since 1.8.1
217 *
218 * @param array $data Core data.
219 * @param Charitable_Campaign_Processor $processor This processor object.
220 */
221 do_action( 'charitable_campaign_processor_save_core', 'update', $campaign_id, $data, $this );
222
223 return $campaign_id;
224 }
225
226 // The returned value can be either the campaign_id or 0 on fail.
227 $campaign_id = wp_insert_post( $data );
228
229 /**
230 * Peform an action after the campaign has been updated.
231 *
232 * @since 1.8.1
233 *
234 * @param array $data Core data.
235 * @param Charitable_Campaign_Processor $processor This processor object.
236 */
237 do_action( 'charitable_campaign_processor_save_core', 'insert', $campaign_id, $data, $this );
238
239 return $campaign_id;
240 }
241
242 /**
243 * Save the campaign meta data.
244 *
245 * @since 1.5.9
246 * @since 1.6.54 Return an array, instead of true.
247 *
248 * @return array The meta data saved, in key=>value format.
249 */
250 public function save_meta() {
251 /**
252 * Filter the campaign meta.
253 *
254 * @since 1.5.9
255 *
256 * @param array $meta Meta data.
257 * @param Charitable_Campaign_Processor $processor This processor object.
258 */
259
260 $meta = apply_filters( 'charitable_campaign_processor_meta_data', charitable_array_subset( $this->args, $this->meta_keys ), $this );
261
262 array_walk( $meta, array( $this, 'save_meta_field' ) );
263
264 return $meta;
265 }
266
267 /**
268 * Returns a truncated key.
269 *
270 * @since 1.5.9
271 *
272 * @param string $key The key to be truncated.
273 * @return string
274 */
275 public function truncated_key( $key ) {
276 return strpos( $key, '_campaign_' ) === 0 ? str_replace( '_campaign_', '', $key ) : $key;
277 }
278
279 /**
280 * Save the campaign meta data.
281 *
282 * @since 1.5.9
283 * @since 1.6.54 Return an array, instead of void.
284 *
285 * @return array Taxonomy data that was saved.
286 */
287 public function save_taxonomies() {
288 /**
289 * Filter the campaign taxonomy data.
290 *
291 * @since 1.5.9
292 *
293 * @param array $data Taxonomy data.
294 * @param Charitable_Campaign_Processor $processor This processor object.
295 */
296 $data = apply_filters(
297 'charitable_campaign_processor_taxonomy_data',
298 array(
299 'campaign_category' => $this->get( 'categories' ),
300 'campaign_tag' => $this->get( 'tags' ),
301 ),
302 $this
303 );
304
305 foreach ( $data as $taxonomy => $terms ) {
306 if ( false !== $terms ) {
307 wp_set_object_terms( $this->campaign_id, $terms, $taxonomy, false );
308 }
309 }
310
311 return $data;
312 }
313
314 /**
315 * Checks whether this is a new campaign being created,
316 * or an update to an existing campaign.
317 *
318 * @since 1.5.9
319 *
320 * @return boolean
321 */
322 public function is_new_campaign() {
323 return ! array_key_exists( 'ID', $this->input ) || 0 == $this->input['ID'];
324 }
325
326 /**
327 * Return the default arguments.
328 *
329 * @since 1.5.9
330 *
331 * @return boolean
332 */
333 protected function get_default_args() {
334 /**
335 * Set the default campaign arguments.
336 *
337 * @since 1.5.9
338 *
339 * @param array $args The list of default arguments.
340 */
341 return apply_filters(
342 'charitable_campaign_processor_default_args',
343 array(
344 'ID' => 0,
345 'title' => '',
346 'content' => '',
347 'creator' => get_current_user_id(),
348 'status' => 'publish',
349 'parent' => 0,
350 'image' => 0,
351 'description' => '',
352 'goal' => 0,
353 'end_date' => 0,
354 'suggested_donations' => array(),
355 'suggested_donations_default' => 0,
356 'allow_custom_donations' => 1,
357 'categories' => array(),
358 'tags' => array(),
359 'minimum_donation_amount' => 0,
360 )
361 );
362 }
363
364 /**
365 * Parse submitted values with the default values.
366 *
367 * @since 1.5.9
368 *
369 * @return array
370 */
371 protected function parse_args() {
372 if ( $this->is_new_campaign() ) {
373 $this->args = $this->get_default_args();
374 }
375
376 /**
377 * Set each passed argument.
378 */
379 array_walk( $this->input, array( $this, 'set_initial_arg' ) );
380
381 /**
382 * Set the campaign ID.
383 */
384 $this->campaign_id = $this->args['ID'];
385
386 return $this->args;
387 }
388
389 /**
390 * Set an arg that was passed when instantiating the object.
391 *
392 * @since 1.5.9
393 *
394 * @param mixed $value The arg value.
395 * @param string $key The arg key.
396 * @return void
397 */
398 protected function set_initial_arg( $value, $key ) {
399 $this->set( $key, $value );
400 }
401
402 /**
403 * Sanitize the campaign status.
404 *
405 * @since 1.5.9
406 *
407 * @param mixed $value The submitted end date.
408 * @return string
409 */
410 protected function sanitize_status( $value ) {
411 if ( ! in_array( $value, get_post_stati() ) ) {
412 $value = 'publish';
413 }
414
415 return $value;
416 }
417
418 /**
419 * Sanitize the campaign creator.
420 *
421 * This checks to ensure that a user actually exists for the given ID.
422 *
423 * @since 1.5.9
424 *
425 * @param mixed $value The submitted creator ID.
426 * @return string
427 */
428 protected function sanitize_creator( $value ) {
429 $user = get_user_by( 'id', $value );
430
431 if ( false === $user ) {
432 $value = 0;
433 }
434
435 return $value;
436 }
437
438 /**
439 * Sanitize the goal.
440 *
441 * @since 1.5.9
442 *
443 * @param string $value Current value of goal.
444 * @return float
445 */
446 protected function sanitize_goal( $value ) {
447 if ( $this->should_skip_sanitizer_for_key( 'goal', array( 'Charitable_Campaign', 'sanitize_campaign_goal' ) ) ) {
448 return $value;
449 }
450
451 return Charitable_Campaign::sanitize_campaign_goal( (string) $value );
452 }
453
454 /**
455 * Sanitize the suggested donations.
456 *
457 * @since 1.5.9
458 *
459 * @param arrat $value Current value of suggested_donations.
460 * @return array
461 */
462 protected function sanitize_suggested_donations( $value ) {
463 if ( $this->should_skip_sanitizer_for_key( 'suggested_donations', array( 'Charitable_Campaign', 'sanitize_campaign_suggested_donations' ) ) ) {
464 return $value;
465 }
466
467 return Charitable_Campaign::sanitize_campaign_suggested_donations( $value );
468 }
469
470 /**
471 * Sanitize the suggested donations default
472 *
473 * @since 1.5.9
474 *
475 * @param arrat $value Current value of suggested_donations.
476 * @return array
477 */
478 protected function sanitize_suggested_donations_default( $value ) {
479 if ( $this->should_skip_sanitizer_for_key( 'suggested_donations_default', array( 'Charitable_Campaign', 'sanitize_campaign_suggested_donations' ) ) ) {
480 return $value;
481 }
482
483 return Charitable_Campaign::sanitize_campaign_suggested_donations_default( $value );
484 }
485
486 /**
487 * Sanitize the custom donations checkbox.
488 *
489 * @since 1.5.9
490 *
491 * @param int $value Current value of custom donations.
492 * @return int
493 */
494 protected function sanitize_allow_custom_donations( $value ) {
495 if ( $this->should_skip_sanitizer_for_key( 'allow_custom_donations', array( 'Charitable_Campaign', 'sanitize_custom_donations' ) ) ) {
496 return $value;
497 }
498
499 return Charitable_Campaign::sanitize_custom_donations( $value, $this->input );
500 }
501
502 /**
503 * Return the input value of the suggested donations, or the existing suggested
504 * donations for the campaign if this is an update.
505 *
506 * @since 1.5.9
507 *
508 * @return array
509 */
510 public function get_suggested_donations_value() {
511 if ( array_key_exists( 'suggested_donations', $this->input ) ) {
512 return $this->input['suggested_donations'];
513 }
514
515 if ( $this->is_new_campaign() ) {
516 return array();
517 }
518
519 return charitable_get_campaign( $this->args['ID'] )->get_suggested_donations();
520 }
521
522 /**
523 * Sanitize the description.
524 *
525 * @since 1.5.9
526 *
527 * @param arrat $value Current value of description.
528 * @return array
529 */
530 protected function sanitize_description( $value ) {
531 if ( $this->should_skip_sanitizer_for_key( 'description', array( 'Charitable_Campaign', 'sanitize_campaign_description' ) ) ) {
532 return $value;
533 }
534
535 return Charitable_Campaign::sanitize_campaign_description( $value );
536 }
537
538 /**
539 * Sanitize the end date.
540 *
541 * @since 1.5.9
542 *
543 * @param mixed $value The submitted end date.
544 * @return string
545 */
546 protected function sanitize_end_date( $value ) {
547 if ( 0 == $value ) {
548 return 0;
549 }
550
551 if ( ! $this->is_mysql_date_format( $value ) ) {
552 charitable_get_deprecated()->doing_it_wrong(
553 __METHOD__,
554 // Translators: %s is the date passed.
555 sprintf( __( 'Campaign processor requires date in YYYY-MM-DD format. Date passed as %s. This message was added in Charitable version 1.5.9.', 'charitable' ), $value ),
556 null
557 );
558
559 return 0;
560 }
561
562 if ( false === strpos( $value, ' ' ) ) {
563 $value .= ' 23:59:59';
564 }
565
566 return $value;
567 }
568
569 /**
570 * Checks whether the passed date is in MySQL date format.
571 *
572 * @since 1.5.9
573 *
574 * @param string $date The date.
575 * @return boolean
576 */
577 protected function is_mysql_date_format( $date ) {
578 return preg_match( '/^\d{4}-\d{2}-\d{2}/', $date );
579 }
580
581 /**
582 * After saving the thumbnail ID, maybe attach its attachment post
583 * to the current campaign.
584 *
585 * @since 1.5.9
586 *
587 * @param int|string $value The thumbnail ID.
588 * @return void
589 */
590 protected function post_process_image( $value ) {
591 if ( empty( $value ) ) {
592 return;
593 }
594
595 $parent = get_post_field( 'post_parent', $value );
596
597 /* If the image is unattached to any post, attach it to the campaign. */
598 if ( 0 === $parent ) {
599 wp_update_post(
600 array(
601 'ID' => $value,
602 'post_parent' => $this->campaign_id,
603 )
604 );
605 }
606 }
607
608 /**
609 * Given a key, checks whether the key is set and if it is,
610 * gets the correct core key (i.e. post_content vs content)
611 * and the value.
612 *
613 * @since 1.5.9
614 *
615 * @return mixed
616 */
617 public function get_core_data_value( $ret, $key ) {
618 if ( is_null( $ret ) ) {
619 $ret = array();
620 }
621
622 if ( array_key_exists( $key, $this->args ) ) {
623 $ret[ array_search( $key, $this->core_keys ) ] = $this->args[ $key ];
624 }
625
626 return $ret;
627 }
628
629 /**
630 * Save a meta field.
631 *
632 * @since 1.5.9
633 *
634 * @param mixed $value The value of the meta field.
635 * @param string $key The key of the meta field.
636 * @return void
637 */
638 public function save_meta_field( $value, $key ) {
639 update_post_meta( $this->campaign_id, $this->get_meta_key( $key ), $value );
640
641 if ( method_exists( $this, 'post_process_' . $key ) ) {
642 $post_process_method = 'post_process_' . $key;
643 $this->$post_process_method( $value );
644 }
645 }
646
647 /**
648 * Returns a sanitized meta key.
649 *
650 * @since 1.5.9
651 *
652 * @param string $key The key to be sanitized.
653 * @return string
654 */
655 protected function get_meta_key( $key ) {
656 if ( 'image' == $key ) {
657 return '_thumbnail_id';
658 }
659
660 $sanitized = strpos( $key, '_campaign_' ) === 0 ? $key : '_campaign_' . $key;
661
662 /**
663 * Filter the meta key.
664 *
665 * @since 1.0.0
666 *
667 * @param string $key The meta key.
668 * @param string $original The original passed key.
669 * @param int $campaign_id The campaign ID.
670 */
671 return apply_filters( 'charitable_campaign_meta_key', $sanitized, $key, $this->campaign_id );
672 }
673
674 /**
675 * Checks whether the original callback function which was hooked for a
676 * particular meta field is still hooked.
677 *
678 * We do this for backwards compatibility reasons. If the function is
679 * still hooked, we do nothing. If it is _not_ hooked, that indicates
680 * that the user used remove_filter() to disable the sanitizer, in
681 * which case we should prevent further sanitization. In this case,
682 * we also fire a notice to let users know that this approach is
683 * deprecated, along with a way to fix it.
684 *
685 * @since 1.5.9
686 *
687 * @param string $key The field key.
688 * @param callback $callback The callback function.
689 * @return boolean
690 */
691 protected function should_skip_sanitizer_for_key( $key, $callback ) {
692 $tag = 'charitable_sanitize_campaign_meta_campaign_' . $key;
693
694 /* We already used the sanitizer. Remove this in Charitable 1.6/1.7. */
695 if ( did_action( $tag ) ) {
696 return true;
697 }
698
699 /* It is still hooked, we do not want to skip the sanitizer. */
700 if ( has_filter( $tag, $callback ) ) {
701 return false;
702 }
703
704 /**
705 * If we get here, the user has disabled the field's default
706 * sanitizer, so we will skip the sanitizer, while firing a
707 * notice to advise that this method of unhooking the sanitizer
708 * is now deprecated.
709 */
710 $method = is_array( $callback ) ? implode( '::', $callback ) : $method;
711
712 charitable_get_deprecated()->doing_it_wrong(
713 $tag,
714 sprintf(
715 // Translators: %1$s is the method, %2$s is the tag, %3$s is the key.
716 __( '%1$s is deprecated as a sanitizer on the %2$s hook. To prevent sanitization, use add_filter( \'charitable_campaign_processor_disable_sanitizer_%3$s\', \'__return_true\' ) instead.', 'charitable' ),
717 $method,
718 $tag,
719 $key
720 ),
721 '1.5.9'
722 );
723
724 return true;
725 }
726
727 /**
728 * Checks whether an internal sanitizer exists for a particular
729 * field, and whether it has been disabled.
730 *
731 * @since 1.5.9
732 *
733 * @param string $key The field key.
734 * @return boolean
735 */
736 protected function use_sanitizer( $key ) {
737 /* If there is no sanitizer, return false. */
738 if ( ! method_exists( $this, 'sanitize_' . $key ) ) {
739 return false;
740 }
741
742 /**
743 * Disable the internal sanitizer for a particular key.
744 *
745 * @since 1.5.9
746 *
747 * @return boolean
748 */
749 return ! apply_filters( 'charitable_campaign_processor_disable_sanitizer_' . $key, false );
750 }
751 }
752
753 endif;
754