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

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

1,537 lines 40.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Campaign model.
4 *
5 * @package Charitable/Classes/Charitable_Campaign
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.0.0
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' ) ) :
19
20 /**
21 * Campaign Model
22 *
23 * @since 1.0.0
24 *
25 * @property int $ID
26 * @property int $post_author
27 * @property string $post_date
28 * @property string $post_date_gmt
29 * @property string $post_content
30 * @property string $post_title
31 * @property string $post_excerpt
32 * @property string $post_status
33 * @property string $comment_status
34 * @property string $ping_status
35 * @property string $post_password
36 * @property string $post_name
37 * @property string $to_ping
38 * @property string $pinged
39 * @property string $post_modified
40 * @property string $post_modified_gmt
41 * @property string $post_content_filtered
42 * @property int $post_parent
43 * @property string $guid
44 * @property int $menu_order
45 * @property string $post_type
46 * @property string $post_mime_type
47 * @property int $comment_count
48 * @property string $filter
49 */
50 class Charitable_Campaign {
51
52 /**
53 * The WP_Post object associated with this campaign.
54 *
55 * @var WP_Post
56 */
57 private $post;
58
59 /**
60 * The timestamp for the expiry for this campaign.
61 *
62 * @var int
63 */
64 private $end_time;
65
66 /**
67 * The fundraising goal for the campaign.
68 *
69 * @var string|false
70 */
71 private $goal;
72
73 /**
74 * The fundraising goal for the campaign.
75 *
76 * @var string|false
77 */
78 private $min_donation_amount;
79
80 /**
81 * The donations made to this campaign.
82 *
83 * @var WP_Query|false
84 */
85 private $donations;
86
87 /**
88 * The amount donated to the campaign.
89 *
90 * @var int|false
91 */
92 private $donated_amount;
93
94 /**
95 * The form object for this campaign.
96 *
97 * @var Charitable_Donation_Form
98 */
99 private $donation_form;
100
101 /**
102 * The Charitable_Object_Fields object for this campaign.
103 *
104 * @since 1.6.0
105 *
106 * @var Charitable_Object_Fields
107 */
108 protected $fields;
109
110 /**
111 * Class constructor.
112 *
113 * @since 1.0.0
114 *
115 * @param mixed $post The post ID or WP_Post object for this this campaign.
116 */
117 public function __construct( $post ) {
118 if ( ! $post instanceof WP_Post ) {
119 $post = get_post( $post );
120 }
121
122 add_filter('charitable_campaign_description_template_content', array( $this, 'description_content' ), 10, 2 );
123
124 $this->post = $post;
125 }
126
127 /**
128 * Return description content.
129 *
130 * @since 1.7.0.4
131 *
132 * @param string $key The key of the field to get.
133 * @return mixed
134 */
135 public function description_content( $content = false, $campaign = false ) {
136
137 // search for YouTube and Vimeo video urls by default and attempt to convert them to oembed
138
139 preg_match_all("#(?<=v=|v\/|vi=|vi\/|youtu.be\/)[a-zA-Z0-9_-]{11}#", $content, $matches);
140
141 if ( $matches ) :
142 foreach ( $matches as $video_id ) {
143 if ( ! empty( $video_id ) ) :
144 $video_embed_args = apply_filters( 'charitable_campaign_description_template_video', array(), 'youtube', $video_id, $content );
145 $embed = wp_oembed_get( 'https://youtube.com/watch?v=' . $video_id[0], $video_embed_args );
146
147 $content = str_replace( trim( 'https://youtube.com/watch?v=' . $video_id[0] ), $embed, $content );
148 $content = str_replace( trim( 'http://youtube.com/watch?v=' . $video_id[0] ), $embed, $content );
149 $content = str_replace( trim( 'https://www.youtube.com/watch?v=' . $video_id[0] ), $embed, $content );
150 $content = str_replace( trim( 'http://www.youtube.com/watch?v=' . $video_id[0] ), $embed, $content );
151 endif;
152 }
153 endif;
154
155 preg_match("/https?:\/\/(?:www\.)?vimeo\.com\/\d{8}/", $content, $matches);
156
157 if ( $matches ) :
158 foreach ( $matches as $video_url ) {
159 if ( ! empty( $video_id ) ) :
160 $video_embed_args = apply_filters( 'charitable_campaign_description_template_video', array(), 'vimeo', $video_id, $content, $campaign );
161 $embed = wp_oembed_get( $video_url, $video_embed_args );
162
163 $content = str_replace( trim( $video_url ), $embed, $content );
164 endif;
165 }
166 endif;
167
168 // allow developers and plugins to hook into content.
169 $content = apply_filters( 'charitable_campaign_description_content', $content, $campaign );
170
171 // apply WP's the_content filter, which other plugins can hook into.
172 $content = apply_filters( 'the_content', $content );
173
174 return $content;
175
176 }
177
178 /**
179 * Magic getter.
180 *
181 * @since 1.0.0
182 *
183 * @param string $key The key of the field to get.
184 * @return mixed
185 */
186 public function __get( $key ) {
187 if ( property_exists( $this->post, $key ) ) {
188 return $this->post->$key;
189 }
190
191 return $this->get( $key );
192 }
193
194 /**
195 * Limit properties to be serialized.
196 *
197 * @since 1.6.18
198 *
199 * @return array
200 */
201 public function __sleep() {
202 return array(
203 'post',
204 'end_time',
205 'goal',
206 'donations',
207 'donated_amount',
208 'donation_form',
209 );
210 }
211
212 /**
213 * Returns the campaign's post_meta values. _campaign_ is automatically prepended to the meta key.
214 *
215 * @see get_post_meta
216 *
217 * @since 1.0.0
218 *
219 * @param string $key The field key.
220 * @param boolean $single Whether to return a single value or an array.
221 * @return mixed This will return an array if single is false. If it's true, the value of the
222 * meta_value field will be returned.
223 */
224 public function get( $key, $single = true ) {
225 if ( $this->fields()->has_value_callback( $key ) ) {
226 return $this->fields()->get( $key );
227 }
228
229 /* Look for a local method. */
230 if ( method_exists( $this, 'get_' . $key ) ) {
231 $method = 'get_' . $key;
232 return $this->$method();
233 }
234
235 return $this->get_meta( '_campaign_' . $key, $single );
236 }
237
238 /**
239 * Return the campaign's post_meta values.
240 *
241 * @since since
242 *
243 * @param string $meta_name The meta key.
244 * @param boolean $single Whether to return a single value or an array.
245 * @return mixed This will return an array if single is false. If it's true, the value of the
246 * meta_value field will be returned.
247 */
248 public function get_meta( $meta_name, $single = true ) {
249 /**
250 * Filter the meta value of a particular campaign field.
251 *
252 * @since 1.0.0
253 *
254 * @param mixed $value The meta value.
255 * @param string $meta_name The name of the meta field.
256 * @param boolean $single Whether we're returning a single result.
257 * @param Charitable_Campaign $campaign This instance of `Charitable_Campaign`.
258 */
259 return apply_filters( 'charitable_campaign_get_meta_value', get_post_meta( $this->post->ID, $meta_name, $single ), $meta_name, $single, $this );
260 }
261
262 /**
263 * Return the Charitable_Object_Fields instance.
264 *
265 * @since 1.6.0
266 *
267 * @return Charitable_Object_Fields
268 */
269 public function fields() {
270 if ( ! isset( $this->fields ) ) {
271 $this->fields = new Charitable_Object_Fields( charitable()->campaign_fields(), $this );
272 }
273
274 return $this->fields;
275 }
276
277 /**
278 * Returns whether the campaign is endless (i.e. no end date has been set).
279 *
280 * @since 1.0.0
281 *
282 * @return boolean
283 */
284 public function is_endless() {
285 return 0 == $this->get_meta( '_campaign_end_date' );
286 }
287
288 /**
289 * Return whether the campaign supports custom donation amounts.
290 *
291 * @since 1.6.53
292 *
293 * @return boolean
294 */
295 public function get_allow_custom_donations() {
296 return (int) get_post_meta( $this->post->ID, '_campaign_allow_custom_donations', true );
297 }
298
299 /**
300 * Return the suggested amounts, or an empty array if there are none.
301 *
302 * @since 1.0.0
303 *
304 * @return array
305 */
306 public function get_suggested_donations() {
307 $value = get_post_meta( $this->post->ID, '_campaign_suggested_donations', true );
308
309 if ( ! is_array( $value ) ) {
310 $value = array();
311 }
312
313 return apply_filters( 'charitable_campaign_suggested_donations', $value, $this );
314 }
315
316 /**
317 * Return the suggested amounts, or an empty array if there are none.
318 *
319 * @since 1.0.0
320 *
321 * @return array
322 */
323 public function get_suggested_donations_default() {
324 $value = get_post_meta( $this->post->ID, '_campaign_suggested_donations_default', true );
325
326 if ( is_array( $value ) && isset( $value[0] ) ) {
327 $value = $value[0];
328 }
329
330 return apply_filters( 'charitable_campaign_suggested_donations_default', $value, $this );
331 }
332
333 /**
334 * Returns the end date in your preferred format.
335 *
336 * If a format is not provided, the user-defined date_format in WordPress settings is used.
337 *
338 * @since 1.0.0
339 *
340 * @param string $date_format A date format accepted by PHP's date() function.
341 * @return string|false String if an end date is set. False if campaign has no end date.
342 */
343 public function get_end_date( $date_format = '' ) {
344 if ( $this->is_endless() ) {
345 return false;
346 }
347
348 if ( ! strlen( $date_format ) ) {
349 $date_format = get_option( 'date_format', 'd/m/Y' );
350 }
351
352 /* Filter the end date format using the charitable_campaign_end_date_format hook. */
353 $date_format = apply_filters( 'charitable_campaign_end_date_format', $date_format, $this );
354
355 /* This is how the end date is stored in the database, so just return that directly. */
356 if ( 'Y-m-d H:i:s' == $date_format ) {
357 return $this->get_meta( '_campaign_end_date' );
358 }
359
360 return date_i18n( $date_format, $this->get_end_time() );
361 }
362
363 /**
364 * Returns the timetamp of the end date.
365 *
366 * @since 1.0.0
367 *
368 * @return int|false Int if campaign has an end date. False if campaign has no end date.
369 */
370 public function get_end_time() {
371 if ( ! isset( $this->end_time ) ) {
372 $end_date = $this->get_meta( '_campaign_end_date' );
373
374 if ( ! $end_date ) {
375 return false;
376 }
377
378 /* The date is stored in the format of Y-m-d H:i:s. */
379 $date_time = explode( ' ', $end_date );
380 $date = explode( '-', $date_time[0] );
381 $time = explode( ':', $date_time[1] );
382
383 $this->end_time = mktime( $time[0], $time[1], $time[2], $date[1], $date[2], $date[0] );
384 }
385
386 return $this->end_time;
387 }
388
389 /**
390 * Returns the amount of time left in the campaign in seconds.
391 *
392 * @since 1.0.0
393 *
394 * @return int $time_left Int if campaign has an end date. False if campaign has no end date.
395 */
396 public function get_seconds_left() {
397 if ( $this->is_endless() ) {
398 return false;
399 }
400
401 $time_left = $this->get_end_time() - current_time( 'timestamp' );
402
403 return $time_left < 0 ? 0 : $time_left;
404 }
405
406 /**
407 * Returns the amount of time left in the campaign as a descriptive string.
408 *
409 * @uses charitable_campaign_ended Change the text displayed when there is no time left.
410 * @uses charitabile_campaign_minutes_left Change the text displayed when there is less than an hour left.
411 * @uses charitabile_campaign_hours_left Change the text displayed when there is less than a day left.
412 * @uses charitabile_campaign_days_left Change the text displayed when there is more than a day left.
413 * @uses charitable_campaign_time_left Change the text displayed when there is time left.
414 *
415 * @since 1.0.0
416 *
417 * @return string
418 */
419 public function get_time_left() {
420 if ( $this->is_endless() ) {
421 return '';
422 }
423
424 $hour = 3600;
425 $day = 86400;
426
427 $seconds_left = $this->get_seconds_left();
428
429 if ( 0 === $seconds_left ) {
430
431 /* Condition 1: The campaign has finished. */
432
433 $time_left = apply_filters( 'charitable_campaign_ended', __( 'Campaign has ended', 'charitable' ), $this );
434
435 } elseif ( $seconds_left <= $hour ) {
436
437 /* Condition 2: There is less than an hour left. */
438
439 $minutes_remaining = ceil( $seconds_left / 60 );
440 $time_left = apply_filters(
441 'charitabile_campaign_minutes_left',
442 sprintf( _n( '%s Minute Left', '%s Minutes Left', $minutes_remaining, 'charitable' ), '<span class="amount time-left minutes-left">' . $minutes_remaining . '</span>' ),
443 $this
444 );
445
446 } elseif ( $seconds_left <= $day ) {
447
448 /* Condition 3: There is less than a day left. */
449
450 $hours_remaining = floor( $seconds_left / 3600 );
451 $time_left = apply_filters(
452 'charitabile_campaign_hours_left',
453 sprintf( _n( '%s Hour Left', '%s Hours Left', $hours_remaining, 'charitable' ), '<span class="amount time-left hours-left">' . $hours_remaining . '</span>' ),
454 $this
455 );
456
457 } else {
458
459 /* Condition 4: There is more than a day left. */
460
461 $days_remaining = floor( $seconds_left / 86400 );
462 $time_left = apply_filters(
463 'charitabile_campaign_days_left',
464 sprintf( _n( '%s Day Left', '%s Days Left', $days_remaining, 'charitable' ), '<span class="amount time-left days-left">' . $days_remaining . '</span>' ),
465 $this
466 );
467
468 }//end if
469
470 return apply_filters( 'charitable_campaign_time_left', $time_left, $this );
471 }
472
473 /**
474 * Returns whether the campaign has ended.
475 *
476 * @since 1.0.0
477 *
478 * @return boolean
479 */
480 public function has_ended() {
481 return ! $this->is_endless() && 0 == $this->get_seconds_left();
482 }
483
484 /**
485 * Returns whether a campaign is currently able to receive donations.
486 *
487 * @since 1.5.0
488 *
489 * @return boolean
490 */
491 public function can_receive_donations() {
492 $can = 'active' == $this->get_status();
493
494 /**
495 * Filter whether the campaign can receive donations.
496 *
497 * @since 1.5.0
498 *
499 * @param boolean $can Whether the campaign can receive donations.
500 * @param Charitable_Campaign $campaign This instance of `Charitable_Campaign`.
501 */
502 return apply_filters( 'charitable_campaign_can_receive_donations', $can, $this );
503 }
504
505 /**
506 * Return a text notice to say that a campaign has finished.
507 *
508 * @since 1.0.0
509 *
510 * @return string
511 */
512 public function get_finished_notice() {
513 if ( ! $this->has_ended() ) {
514 return '';
515 }
516
517 if ( ! $this->has_goal() ) {
518 $message = __( 'This campaign ended %s ago', 'charitable' );
519 } elseif ( $this->has_achieved_goal() ) {
520 $message = __( 'This campaign successfully reached its funding goal and ended %s ago', 'charitable' );
521 } else {
522 $message = __( 'This campaign failed to reach its funding goal %s ago', 'charitable' );
523 }
524
525 return apply_filters( 'charitable_campaign_finished_notice', sprintf( $message, '<span class="time-ago">' . human_time_diff( $this->get_end_time() ) . '</span>' ), $this );
526 }
527
528 /**
529 * Return the time since the campaign finished, or zero if it's still going.
530 *
531 * @since 1.0.0
532 *
533 * @return int
534 */
535 public function get_time_since_ended() {
536 if ( 0 !== $this->get_seconds_left() ) {
537 return 0;
538 }
539
540 return current_time( 'U' ) - $this->get_end_time();
541 }
542
543 /**
544 * Returns the fundraising goal of the campaign.
545 *
546 * @since 1.0.0
547 *
548 * @return string|false Amount if goal is set. False otherwise.
549 */
550 public function get_goal() {
551 if ( ! isset( $this->goal ) ) {
552 $this->goal = $this->has_goal() ? $this->get_meta( '_campaign_goal' ) : false;
553 }
554
555 return $this->goal;
556 }
557
558 /**
559 * Returns the minimum donation amount of the campaign.
560 *
561 * @since 1.0.0
562 *
563 * @return string|false Amount if goal is set. False otherwise.
564 */
565 public function get_min_donation_amount() {
566 if ( ! isset( $this->min_donation_amount ) ) {
567 $this->min_donation_amount = $this->get_meta( '_campaign_min_donation_amount' ) ? $this->get_meta( '_campaign_min_donation_amount' ) : false;
568 }
569
570 return $this->min_donation_amount;
571 }
572
573
574 /**
575 * Returns whether a goal has been set (anything greater than $0 is a goal).
576 *
577 * @since 1.0.0
578 *
579 * @return boolean
580 */
581 public function has_goal() {
582 return 0 < (float) $this->get_meta( '_campaign_goal' );
583 }
584
585 /**
586 * Returns the fundraising goal formatted as a monetary amount.
587 *
588 * @since 1.0.0
589 *
590 * @return string
591 */
592 public function get_monetary_goal() {
593 if ( ! $this->has_goal() ) {
594 return '';
595 }
596
597 return charitable_format_money( (string) $this->get_meta( '_campaign_goal' ) );
598 }
599
600 /**
601 * Returns whether the goal has been achieved.
602 *
603 * @since 1.0.0
604 *
605 * @return boolean
606 */
607 public function has_achieved_goal() {
608 return $this->get_donated_amount( true ) >= $this->get_goal();
609 }
610
611 /**
612 * Return a message to say whether the campaign has achieved its goal.
613 *
614 * @since 1.6.0
615 *
616 * @param string $success Optional. The success message.
617 * @param string $failure Optional. The failure message.
618 * @return string
619 */
620 public function get_goal_achieved_message( $success = '', $failure = '' ) {
621 if ( $this->has_achieved_goal() ) {
622
623 if ( strlen( $success ) ) {
624 return $success;
625 }
626
627 /**
628 * Filter the default message to show when a campaign has achieved its goal.
629 *
630 * @since 1.6.0
631 *
632 * @param string $message The success message.
633 * @param Charitable_Campaign $campaign This instance of `Charitable_Campaign`.
634 */
635 return apply_filters( 'charitable_campaign_goal_achievement_success_message', __( 'The campaign achieved its fundraising goal.', 'charitable' ), $this );
636 }
637
638 if ( strlen( $failure ) ) {
639 return $failure;
640 }
641
642 /**
643 * Filter the default message to show when a campaign has not achieved its goal.
644 *
645 * @since 1.6.0
646 *
647 * @param string $message The failure message.
648 * @param Charitable_Campaign $campaign This instance of `Charitable_Campaign`.
649 */
650 return apply_filters( 'charitable_campaign_goal_achievement_failure_message', __( 'The campaign did not reach its fundraising goal.', 'charitable' ), $this );
651 }
652
653 /**
654 * Return the campaign status.
655 *
656 * If the campaign is published, this will return whether either 'active' or 'finished'.
657 *
658 * @since 1.0.0
659 *
660 * @return string
661 */
662 public function get_status() {
663 $status = $this->post->post_status;
664
665 if ( 'publish' == $status ) {
666 $status = $this->has_ended() ? 'finished' : 'active';
667 }
668
669 /**
670 * Filter the returned campaign status.
671 *
672 * @since 1.0.0
673 *
674 * @param string $status The campaign status.
675 * @param string $post_status The post_status of the campaign.
676 * @param Charitable_Campaign $campaign This instance of `Charitable_Campaign`.
677 */
678 return apply_filters( 'charitable_campaign_status', $status, $this->post->post_status, $this );
679 }
680
681 /**
682 * Return a status key for the campaign.
683 *
684 * This will return one of the following:
685 *
686 * inactive : A campaign that is not published.
687 * ended : A campaign without a goal has finished
688 * successful : A campaign with a goal has finished & achieved its goal
689 * unsucessful : A campaign with a goal has finished without achieving its goal
690 * ending : A campaign is ending soon.
691 * active : A campaign that is active and not ending soon.
692 *
693 * @since 1.3.7
694 *
695 * @return string
696 */
697 public function get_status_key() {
698 /**
699 * Set the threshold in seconds for defining whether a campaign is ending soon.
700 *
701 * @since 1.0.0
702 *
703 * @param int $seconds By default, this equals 604,800.
704 */
705 $ending_soon_threshold = apply_filters( 'charitable_campaign_ending_soon_threshold', WEEK_IN_SECONDS );
706 $ended = $this->has_ended();
707
708 if ( 'publish' != $this->post->post_status ) {
709 return 'inactive';
710 }
711
712 if ( $ended && ! $this->has_goal() ) {
713 return 'ended';
714 }
715
716 if ( $ended && $this->has_achieved_goal() ) {
717 return 'successful';
718 }
719
720 if ( $ended ) {
721 return 'unsucessful';
722 }
723
724 if ( ! $this->is_endless() && $this->get_seconds_left() < $ending_soon_threshold ) {
725 return 'ending';
726 }
727
728 return 'active';
729 }
730
731 /**
732 * Return the campaign status tag.
733 *
734 * @since 1.0.0
735 *
736 * @return string
737 */
738 public function get_status_tag( $show_achievement = null, $show_active_tag = null ) {
739 $key = $this->get_status_key();
740
741 if ( is_null( $show_achievement ) ) {
742 $show_achievement = apply_filters( 'charitable_campaign_show_achievement_status_tag', true );
743 }
744
745 if ( is_null( $show_active_tag ) ) {
746 $show_active_tag = apply_filters( 'charitable_campaign_show_active_status_tag', false );
747 }
748
749 switch ( $key ) {
750 case 'ended':
751 $tag = __( 'Ended', 'charitable' );
752 break;
753
754 case 'successful':
755 $tag = $show_achievement ? __( 'Successful', 'charitable' ) : __( 'Ended', 'charitable' );
756 break;
757
758 case 'unsucessful':
759 $tag = $show_achievement ? __( 'Unsuccessful', 'charitable' ) : __( 'Ended', 'charitable' );
760 break;
761
762 case 'ending':
763 $tag = __( 'Ending Soon', 'charitable' );
764 break;
765
766 case 'active':
767 case 'publish':
768 $tag = $show_active_tag ? __( 'Active', 'charitable' ) : '';
769 break;
770
771 case 'inactive':
772 switch ( $this->post->post_status ) {
773 case 'pending':
774 $tag = __( 'Pending', 'charitable' );
775 break;
776
777 case 'draft':
778 $tag = __( 'Draft', 'charitable' );
779 break;
780
781 case 'future':
782 $tag = __( 'Scheduled', 'charitable' );
783 break;
784
785 default:
786 $tag = '';
787 }
788 break;
789
790 default:
791 $tag = '';
792
793 }//end switch
794
795 /**
796 * Filter the campaign status tag.
797 *
798 * @since 1.0.0
799 *
800 * @param string $tag The tag for the status.
801 * @param string $key The key of the current status.
802 * @param Charitable_Campaign $this This campaign object.
803 */
804 return apply_filters( 'charitable_campaign_status_tag', $tag, $key, $this );
805 }
806
807 /**
808 * Return the permalink for this campaign.
809 *
810 * @since 1.6.0
811 *
812 * @return string|false The permalink URL or false if post does not exist.
813 */
814 public function get_permalink() {
815 return get_permalink( $this->ID );
816 }
817
818 /**
819 * Return the admin edit link for this campaign.
820 *
821 * @since 1.6.0
822 *
823 * @return string
824 */
825 public function get_admin_edit_link() {
826 $post_type_object = get_post_type_object( Charitable::CAMPAIGN_POST_TYPE );
827
828 if ( $post_type_object->_edit_link ) {
829 $link = admin_url( sprintf( $post_type_object->_edit_link . '&amp;action=edit', $this->ID ) );
830 } else {
831 $link = '';
832 }
833
834 return $link;
835 }
836
837 /**
838 * Returns the donations made to this campaign.
839 *
840 * @since 1.0.0
841 *
842 * @return WP_Query
843 */
844 public function get_donations() {
845 $this->donations = get_transient( self::get_donations_cache_key( $this->ID ) );
846
847 if ( false === $this->donations ) {
848
849 $this->donations = charitable_get_table( 'campaign_donations' )->get_donations_on_campaign( $this->ID );
850
851 set_transient( self::get_donations_cache_key( $this->ID ), $this->donations, 0 );
852 }
853
854 return $this->donations;
855 }
856
857 /**
858 * Return the current amount of donations.
859 *
860 * @since 1.0.0
861 *
862 * @param boolean $sanitize Whether to sanitize the amount. False by default.
863 * @return string String if $sanitize is false. If $sanitize is true, return a float or WP_Error if the amount is not a string.
864 */
865 public function get_donated_amount( $sanitize = false ) {
866 $this->donated_amount = get_transient( self::get_donation_amount_cache_key( $this->ID ) );
867
868 if ( false === $this->donated_amount ) {
869 $this->donated_amount = charitable_get_table( 'campaign_donations' )->get_campaign_donated_amount( $this->ID, false, false );
870
871 set_transient( self::get_donation_amount_cache_key( $this->ID ), $this->donated_amount, 0 );
872 }
873
874 $amount = $this->donated_amount;
875
876 /* If returning a string, return it in the site number format. */
877 if ( ! $sanitize ) {
878 $amount = Charitable_Currency::get_instance()->sanitize_database_amount( $amount );
879 }
880
881 /**
882 * Filter the campaign donated amount.
883 *
884 * @since 1.2.0
885 *
886 * @param string|float|WP_Error $amount The donated amount.
887 * @param Charitable_Campaign $this This campaign object.
888 * @param boolean $sanitize Whether the amount should be sanitized.
889 */
890 return apply_filters( 'charitable_campaign_donated_amount', $amount, $this, $sanitize );
891 }
892
893 /**
894 * Return the currency formatted donation amount.
895 *
896 * @since 1.6.0
897 *
898 * @return string
899 */
900 public function get_donated_amount_formatted() {
901 return charitable_format_money( $this->get_donated_amount() );
902 }
903
904 /**
905 * Return a string describing the campaign's donation summary.
906 *
907 * @since 1.0.0
908 *
909 * @return string
910 */
911 public function get_donation_summary() {
912 $currency_helper = charitable_get_currency_helper();
913 $amount = $this->get_donated_amount();
914 $goal = $this->get_meta( '_campaign_goal' );
915
916 if ( $goal ) {
917 $ret = sprintf(
918 _x( '%1$s donated of %2$s goal', 'amount donated of goal', 'charitable' ),
919 '<span class="amount">' . $currency_helper->get_monetary_amount( $amount ) . '</span>',
920 '<span class="goal-amount">' . $currency_helper->get_monetary_amount( $goal ) . '</span>'
921 );
922 } else {
923 $ret = sprintf(
924 _x( '%s donated', 'amount donated', 'charitable' ),
925 '<span class="amount">' . $currency_helper->get_monetary_amount( $amount ) . '</span>'
926 );
927 }
928
929 /**
930 * Filter the returned string if you want to display the donation summary in a different way.
931 *
932 * @since 1.0.0
933 * @since 1.5.0 Added the $amount argument.
934 * @since 1.5.0 Added the $goal argument.
935 *
936 * @param string $ret The summary.
937 * @param Charitable_Campaign $this This campaign object.
938 * @param float $amount The amount donated.
939 * @param int $goal The campaign goal.
940 */
941 return apply_filters( 'charitable_donation_summary', $ret, $this, $amount, $goal );
942 }
943
944 /**
945 * Return the percentage donated. Use this if you want a formatted string.
946 *
947 * @since 1.0.0
948 *
949 * @return string|false String if campaign has a goal. False if no goal is set.
950 */
951 public function get_percent_donated() {
952 $percent = $this->get_percent_donated_raw();
953
954 if ( false === $percent ) {
955 return $percent;
956 }
957
958 $percent = number_format( $percent, 2 );
959
960 return apply_filters( 'charitable_percent_donated', $percent . '%', $percent, $this );
961 }
962
963 /**
964 * Returns the percentage donated as a number.
965 *
966 * @since 1.0.0
967 *
968 * @return int
969 */
970 public function get_percent_donated_raw() {
971 if ( ! $this->has_goal() ) {
972 return false;
973 }
974
975 return ( $this->get_donated_amount( true ) / $this->get_goal() ) * 100;
976 }
977
978 /**
979 * Return the number of people who have donated to the campaign.
980 *
981 * @since 1.0.0
982 *
983 * @return int
984 */
985 public function get_donor_count() {
986 /**
987 * Set the number of donors who have donated to this campaign.
988 *
989 * @since 1.4.7
990 *
991 * @param int $count Number of donors.
992 * @param Charitable_Campaign $this This campaign object.
993 */
994 return apply_filters(
995 'charitable_campaign_donor_count',
996 (int) charitable_get_table( 'campaign_donations' )->count_campaign_donors( $this->ID ),
997 $this
998 );
999 }
1000
1001 /**
1002 * Returns the donation form object.
1003 *
1004 * @since 1.0.0
1005 *
1006 * @return Charitable_Donation_Form_Interface
1007 */
1008 public function get_donation_form() {
1009 if ( ! isset( $this->donation_form ) ) {
1010 /**
1011 * Return the class name of the donation form for this campaign.
1012 *
1013 * @since 1.0.0
1014 *
1015 * @param string $class The form class.
1016 * @param Charitable_Campaign $this This campaign object.
1017 */
1018 $form_class = apply_filters( 'charitable_donation_form_class', 'Charitable_Donation_Form', $this );
1019
1020 $this->donation_form = new $form_class( $this );
1021 }
1022
1023 return $this->donation_form;
1024 }
1025
1026 /**
1027 * Returns the amount to be donated to the campaign as it is currently set in the session.
1028 *
1029 * @since 1.0.0
1030 *
1031 * @return int
1032 */
1033 public function get_donation_amount_in_session() {
1034 $donation = charitable_get_session()->get_donation_by_campaign( $this->ID );
1035 $amount = is_array( $donation ) ? $donation['amount'] : 0;
1036
1037 /**
1038 * The donation amount for this campaign stored in the session.
1039 *
1040 * @since 1.0.0
1041 *
1042 * @param float|int $amount The amount.
1043 * @param Charitable_Campaign $this This campaign object.
1044 */
1045 return apply_filters( 'charitable_session_donation_amount', $amount, $this );
1046 }
1047
1048 /**
1049 * Returns the donation period for the amount in session.
1050 *
1051 * @since 1.6.25
1052 *
1053 * @return false|string
1054 */
1055 public function get_donation_period_in_session() {
1056 $donation = charitable_get_session()->get_donation_by_campaign( $this->ID );
1057
1058 if ( ! $donation ) {
1059 return false;
1060 }
1061
1062 if ( ! is_array( $donation ) || ! array_key_exists( 'donation_period', $donation ) ) {
1063 return 'once';
1064 }
1065
1066 return $donation['donation_period'];
1067 }
1068
1069 /**
1070 * Return the initial/default donation period when
1071 *
1072 * @since 1.6.32
1073 *
1074 * @return string
1075 */
1076 public function get_initial_donation_period() {
1077 $period = $this->get_donation_period_in_session();
1078
1079 if ( false !== $period ) {
1080 return $period;
1081 }
1082
1083 if ( ! class_exists( 'Charitable_Recurring' ) ) {
1084 return 'once';
1085 }
1086
1087 switch ( $this->get( 'recurring_donation_mode' ) ) {
1088 case 'advanced':
1089 $period = 'one-time' === $this->get( 'recurring_default_tab' ) ? 'once' : 'recurring';
1090 break;
1091
1092 default:
1093 $period = 'once';
1094 }
1095
1096 return $period;
1097 }
1098
1099 /**
1100 * Get the default donation amount for this campaign.
1101 *
1102 * @since 1.6.32
1103 *
1104 * @return int
1105 */
1106 public function get_default_donation_amount() {
1107 /**
1108 * Filter the default donation amount.
1109 *
1110 * Note, the donation amount should be expressed in a format where
1111 * a period (.) is used as the decimal separator. i.e. 2.50 is
1112 * two dollars and fifty cents.
1113 *
1114 * @since 1.5.6
1115 *
1116 * @param float|int $amount The amount to be filtered. $0 by default.
1117 * @param Charitable_Campaign $campaign The instance of `Charitable_Campaign`.
1118 */
1119 return apply_filters( 'charitable_default_donation_amount', 0, $this );
1120 }
1121
1122 /**
1123 * Renders the donate button template.
1124 *
1125 * @since 1.0.0
1126 *
1127 * @return void
1128 */
1129 public function donate_button_template() {
1130 if ( ! $this->can_receive_donations() ) {
1131 return;
1132 }
1133
1134 $display_option = charitable_get_option( 'donation_form_display', 'separate_page' );
1135
1136 switch ( $display_option ) {
1137 case 'separate_page':
1138 $template_name = 'campaign/donate-button.php';
1139 break;
1140
1141 case 'same_page':
1142 $template_name = 'campaign/donate-link.php';
1143 break;
1144
1145 case 'modal':
1146 $template_name = 'campaign/donate-modal.php';
1147 break;
1148
1149 default:
1150 $template_name = apply_filters( 'charitable_donate_button_template', 'campaign/donate-button.php', $this );
1151 }
1152
1153 charitable_template( $template_name, array( 'campaign' => $this ) );
1154 }
1155
1156 /**
1157 * Renders the donate button template.
1158 *
1159 * @since 1.2.3
1160 *
1161 * @return void
1162 */
1163 public function donate_button_loop_template() {
1164 if ( ! $this->can_receive_donations() ) {
1165 return;
1166 }
1167
1168 $display_option = charitable_get_option( 'donation_form_display', 'separate_page' );
1169
1170 switch ( $display_option ) {
1171 case 'modal':
1172 $template_name = 'campaign-loop/donate-modal.php';
1173 break;
1174
1175 default:
1176 $template_name = apply_filters( 'charitable_donate_button_loop_template', 'campaign-loop/donate-link.php', $this );
1177 }
1178
1179 charitable_template( $template_name, array( 'campaign' => $this ) );
1180 }
1181
1182 /**
1183 * Returns the campaign creator.
1184 *
1185 * By default, this just returns the user from the post_author field, but
1186 * it can be overridden by plugins.
1187 *
1188 * @since 1.0.0
1189 *
1190 * @return int $user_id
1191 */
1192 public function get_campaign_creator() {
1193 return apply_filters( 'charitable_campaign_creator', $this->post->post_author, $this );
1194 }
1195
1196 /**
1197 * Return the campaign creator's name.
1198 *
1199 * @since 1.6.0
1200 *
1201 * @return string
1202 */
1203 public function get_campaign_creator_name() {
1204 return get_the_author_meta( 'display_name', $this->get_campaign_creator() );
1205 }
1206
1207 /**
1208 * Return the campaign creator's email address.
1209 *
1210 * @since 1.6.0
1211 *
1212 * @return string
1213 */
1214 public function get_campaign_creator_email() {
1215 return get_the_author_meta( 'user_email', $this->get_campaign_creator() );
1216 }
1217
1218 /**
1219 * Sanitize the campaign goal.
1220 *
1221 * @since 1.0.0
1222 *
1223 * @param string $value Current value of goal.
1224 * @return string|int
1225 */
1226 public static function sanitize_campaign_goal( $value ) {
1227 if ( empty( $value ) || ! $value ) {
1228 return 0;
1229 }
1230
1231 return charitable_get_currency_helper()->sanitize_monetary_amount( $value );
1232 }
1233
1234 /**
1235 * Sanitize the campaign minimum amount.
1236 *
1237 * @since 1.7.0.3
1238 *
1239 * @param string $value Current value of min amount.
1240 * @return string|int
1241 */
1242 public static function sanitize_min_donation_amount( $value ) {
1243 if ( empty( $value ) || ! $value ) {
1244 return 0;
1245 }
1246
1247 return charitable_get_currency_helper()->sanitize_monetary_amount( $value );
1248 }
1249
1250 /**
1251 * Sanitize the campaign end date.
1252 *
1253 * We use WP_Locale to parse the month that the user has set.
1254 *
1255 * @global WP_Locale $wp_locale
1256 *
1257 * @since 1.0.0
1258 *
1259 * @param string $value Current end date value.
1260 * @param array $submitted The submitted data.
1261 * @return string|int
1262 */
1263 public static function sanitize_campaign_end_date( $value, $submitted = array() ) {
1264 $end_time = array_key_exists( '_campaign_end_time', $submitted ) ? $submitted['_campaign_end_time'] : '23:59:59';
1265
1266 if ( charitable()->registry()->get( 'i18n' )->decline_months() ) {
1267 $end_date = $value . ' ' . $end_time;
1268 } else {
1269 $end_date = charitable_sanitize_date( $value, 'Y-m-d ' . $end_time );
1270 }
1271
1272 if ( ! $end_date ) {
1273 $end_date = 0;
1274 }
1275
1276 return $end_date;
1277 }
1278
1279 /**
1280 * Sanitize the campaign suggested donations.
1281 *
1282 * @since 1.0.0
1283 *
1284 * @param array $value Current suggested donations value.
1285 * @return array
1286 */
1287 public static function sanitize_campaign_suggested_donations_default( $value ) {
1288
1289 return $value; // this is likely an array.
1290
1291 }
1292
1293 /**
1294 * Sanitize the campaign suggested donations.
1295 *
1296 * @since 1.0.0
1297 *
1298 * @param array $value Current suggested donations value.
1299 * @return array
1300 */
1301 public static function sanitize_campaign_suggested_donations( $value, $submitted = false, $campaign_id = false ) {
1302 if ( ! is_array( $value ) ) {
1303 return array();
1304 }
1305
1306 $value = array_filter( $value, array( 'Charitable_Campaign', 'filter_suggested_donation' ) );
1307
1308 if ( empty( $value ) ) {
1309 return $value;
1310 }
1311
1312 foreach ( $value as $key => $suggestion ) {
1313 $value[ $key ]['amount'] = charitable_sanitize_amount( (string) $suggestion['amount'] );
1314
1315 /**
1316 * Sanitize the description field.
1317 *
1318 * @since 1.6.51
1319 *
1320 * @param boolean $sanitize Whether to sanitize the suggested amount description.
1321 * Returning false will allow HTML to be used, though it will
1322 * still be filtered using wp_kses_post.
1323 */
1324 if ( apply_filters( 'charitable_sanitize_suggested_amount_description', true ) ) {
1325 $value[ $key ]['description'] = sanitize_text_field( $suggestion['description'] );
1326 } else {
1327 $value[ $key ]['description'] = wp_kses_post( $suggestion['description'] );
1328 }
1329 }
1330
1331 return $value;
1332 }
1333
1334 /**
1335 * Filter out any suggested donations that do not have an amount set.
1336 *
1337 * @since 1.0.0
1338 *
1339 * @param array|string $donation Suggested donation or an array of suggested donations.
1340 * @return boolean
1341 */
1342 public static function filter_suggested_donation( $donation ) {
1343 if ( is_array( $donation ) ) {
1344 return isset( $donation['amount'] ) && ! empty( $donation['amount'] );
1345 }
1346
1347 return ! empty( $donation['amount'] );
1348 }
1349
1350 /**
1351 * Sanitize any checkbox value.
1352 *
1353 * @since 1.0.0
1354 *
1355 * @param mixed $value Current checkbox value.
1356 * @return boolean
1357 */
1358 public static function sanitize_checkbox( $value ) {
1359 return intval( true == $value || 'on' === $value );
1360 }
1361
1362 /**
1363 * Sanitize the campaign description.
1364 *
1365 * @since 1.0.0
1366 * @updated 1.7.0.4
1367 *
1368 * @param string $value Current description value.
1369 * @return string
1370 */
1371 public static function sanitize_campaign_description( $value ) {
1372
1373 /**
1374 * Allowed HTML in description.
1375 */
1376 $wp_kses_allowed_html = array(
1377 'a' => array(
1378 'href' => array(),
1379 'target' => array(),
1380 'class' => array(),
1381 'title' => array(),
1382 ),
1383 'br' => array(),
1384 'img' => array(
1385 'src' => array(),
1386 'class' => array(),
1387 'alt' => array(),
1388 ),
1389 'h1' => array(
1390 'class' => array(),
1391 ),
1392 'h2' => array(
1393 'class' => array(),
1394 ),
1395 'h3' => array(
1396 'class' => array(),
1397 ),
1398 'h4' => array(
1399 'class' => array(),
1400 ),
1401 'h5' => array(
1402 'class' => array(),
1403 ),
1404 'h6' => array(
1405 'class' => array(),
1406 ),
1407 'div' => array(
1408 'class' => array(),
1409 ),
1410 'p' => array(
1411 'style' => array(),
1412 'class' => array(),
1413 ),
1414 'ul' => array(
1415 'class' => array(),
1416 ),
1417 'li' => array(
1418 'id' => array(),
1419 'class' => array(),
1420 ),
1421 'em' => array(),
1422 'span' => array(
1423 'class' => array(),
1424 ),
1425 'strong' => array(),
1426 );
1427
1428 if ( apply_filters( 'charitable_sanitize_campaign_description', false ) ) {
1429 return sanitize_text_field( $value );
1430 } else {
1431 return wp_kses( apply_filters( 'charitable_campaign_description', $value ), apply_filters( 'charitable_sanitize_campaign_description_allowed_html', $wp_kses_allowed_html ) );
1432 }
1433
1434 }
1435
1436 /**
1437 * Sanitize the value provided for custom donations.
1438 *
1439 * @since 1.3.6
1440 *
1441 * @param mixed $value Current custom donations value.
1442 * @param array $submitted All posted values.
1443 * @return boolean
1444 */
1445 public static function sanitize_custom_donations( $value, $submitted ) {
1446 $checked = self::sanitize_checkbox( $value );
1447
1448 if ( $checked ) {
1449 return $checked;
1450 }
1451
1452 /**
1453 * Filter whether a campaign can be saved with custom donations disabled
1454 * and no suggested donations.
1455 *
1456 * @since 1.6.14
1457 *
1458 * @param boolean $permitted Whether a campaign is permitted to be saved without
1459 * suggested donations or custom donations.
1460 * @param array $submitted The submitted values.
1461 */
1462 if ( apply_filters( 'charitable_campaign_permitted_without_custom_or_suggested', false, $submitted ) ) {
1463 return $checked;
1464 }
1465
1466 /* If suggested donations are not set, custom donations needs to be enabled. */
1467 if ( ! isset( $submitted['_campaign_suggested_donations'] ) ) {
1468 return 1;
1469 }
1470
1471 $suggested_donations = self::sanitize_campaign_suggested_donations( $submitted['_campaign_suggested_donations'] );
1472
1473 if ( empty( $suggested_donations ) ) {
1474 return 1;
1475 }
1476
1477 return $checked;
1478 }
1479
1480 /**
1481 * Flush donations cache.
1482 *
1483 * @since 1.0.0
1484 *
1485 * @param int $campaign_id The campaign ID.
1486 * @return void
1487 */
1488 public static function flush_donations_cache( $campaign_id ) {
1489 delete_transient( self::get_donations_cache_key( $campaign_id ) );
1490 delete_transient( self::get_donation_amount_cache_key( $campaign_id ) );
1491
1492 do_action( 'charitable_flush_campaign_cache', $campaign_id );
1493 }
1494
1495 /**
1496 * Returns the key used for caching all donations made to this campaign.
1497 *
1498 * @since 1.0.0
1499 *
1500 * @param int $campaign_id The ID of the campaign.
1501 * @return string
1502 */
1503 private static function get_donations_cache_key( $campaign_id ) {
1504 return 'charitable_campaign_' . $campaign_id . '_donations';
1505 }
1506
1507 /**
1508 * Returns the key used for caching the donation amount for this campaign.
1509 *
1510 * @since 1.0.0
1511 *
1512 * @param int $campaign_id The ID of the campaign.
1513 * @return string
1514 */
1515 private static function get_donation_amount_cache_key( $campaign_id ) {
1516 return 'charitable_campaign_' . $campaign_id . '_donation_amount';
1517 }
1518
1519 /**
1520 * Deprecated method used to sanitize meta.
1521 *
1522 * @deprecated 1.7.0
1523 *
1524 * @since 1.4.12 Deprecated.
1525 *
1526 * @param mixed $value Value of meta field.
1527 * @param string $key Key of meta field.
1528 * @param array $submitted Posted values.
1529 */
1530 public static function sanitize_meta( $value, $key, $submitted ) {
1531 charitable_get_deprecated()->deprecated_function( __METHOD__, '1.4.2' );
1532 return apply_filters( 'charitable_sanitize_campaign_meta' . $key, $value, $submitted );
1533 }
1534 }
1535
1536 endif;
1537