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

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