PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.7.0.2
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.7.0.2
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.7.0.2, at includes/campaigns/class-charitable-campaign-processor.php

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