← All changes
|
includes/campaigns/class-charitable-campaign.php
+378
-16
1.7.0
→
1.8.13
View file →
| @@ -3,9 +3,9 @@ | ||
| 3 | 3 | * Campaign model. |
| 4 | 4 | * |
| 5 | 5 | * @package Charitable/Classes/Charitable_Campaign |
| 6 | 6 | * @author David Bisset |
| 7 | - * @copyright Copyright (c) 2022, WP Charitable LLC | |
| 7 | + * @copyright Copyright (c) 2023, WP Charitable LLC | |
| 8 | 8 | * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License |
| 9 | 9 | * @since 1.0.0 |
| 10 | 10 | * @version 1.6.54 |
| 11 | 11 | */ |
| @@ -70,8 +70,15 @@ | ||
| 70 | 70 | */ |
| 71 | 71 | private $goal; |
| 72 | 72 | |
| 73 | 73 | /** |
| 74 | + * The fundraising goal for the campaign. | |
| 75 | + * | |
| 76 | + * @var string|false | |
| 77 | + */ | |
| 78 | + private $min_donation_amount; | |
| 79 | + | |
| 80 | + /** | |
| 74 | 81 | * The donations made to this campaign. |
| 75 | 82 | * |
| 76 | 83 | * @var WP_Query|false |
| 77 | 84 | */ |
| @@ -111,12 +118,65 @@ | ||
| 111 | 118 | if ( ! $post instanceof WP_Post ) { |
| 112 | 119 | $post = get_post( $post ); |
| 113 | 120 | } |
| 114 | 121 | |
| 122 | + add_filter( 'charitable_campaign_description_template_content', array( $this, 'description_content' ), 10, 2 ); | |
| 123 | + | |
| 115 | 124 | $this->post = $post; |
| 116 | 125 | } |
| 117 | 126 | |
| 118 | 127 | /** |
| 128 | + * Return description content. | |
| 129 | + * | |
| 130 | + * @since 1.7.0.4 | |
| 131 | + * | |
| 132 | + * @param string $content The content. | |
| 133 | + * @param object $campaign The campaign object. | |
| 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 ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- 'the_content' is a WordPress core filter hook. | |
| 174 | + | |
| 175 | + return $content; | |
| 176 | + } | |
| 177 | + | |
| 178 | + /** | |
| 119 | 179 | * Magic getter. |
| 120 | 180 | * |
| 121 | 181 | * @since 1.0.0 |
| 122 | 182 | * |
| @@ -123,9 +183,9 @@ | ||
| 123 | 183 | * @param string $key The key of the field to get. |
| 124 | 184 | * @return mixed |
| 125 | 185 | */ |
| 126 | 186 | public function __get( $key ) { |
| 127 | - if ( property_exists( $this->post, $key ) ) { | |
| 187 | + if ( null !== $this->post && property_exists( $this->post, $key ) ) { | |
| 128 | 188 | return $this->post->$key; |
| 129 | 189 | } |
| 130 | 190 | |
| 131 | 191 | return $this->get( $key ); |
| @@ -185,8 +245,13 @@ | ||
| 185 | 245 | * @return mixed This will return an array if single is false. If it's true, the value of the |
| 186 | 246 | * meta_value field will be returned. |
| 187 | 247 | */ |
| 188 | 248 | public function get_meta( $meta_name, $single = true ) { |
| 249 | + | |
| 250 | + if ( empty( $this->post->ID ) ) { | |
| 251 | + return; | |
| 252 | + } | |
| 253 | + | |
| 189 | 254 | /** |
| 190 | 255 | * Filter the meta value of a particular campaign field. |
| 191 | 256 | * |
| 192 | 257 | * @since 1.0.0 |
| @@ -214,8 +279,20 @@ | ||
| 214 | 279 | return $this->fields; |
| 215 | 280 | } |
| 216 | 281 | |
| 217 | 282 | /** |
| 283 | + * Returns the id of the campaign (post). | |
| 284 | + * | |
| 285 | + * @since 1.7.0.9 | |
| 286 | + * @version 1.8.0 | |
| 287 | + * | |
| 288 | + * @return integer | |
| 289 | + */ | |
| 290 | + public function get_campaign_id() { | |
| 291 | + return ( isset( $this->post->ID ) ) ? intval( $this->post->ID ) : false; | |
| 292 | + } | |
| 293 | + | |
| 294 | + /** | |
| 218 | 295 | * Returns whether the campaign is endless (i.e. no end date has been set). |
| 219 | 296 | * |
| 220 | 297 | * @since 1.0.0 |
| 221 | 298 | * |
| @@ -253,8 +330,25 @@ | ||
| 253 | 330 | return apply_filters( 'charitable_campaign_suggested_donations', $value, $this ); |
| 254 | 331 | } |
| 255 | 332 | |
| 256 | 333 | /** |
| 334 | + * Return the suggested amounts, or an empty array if there are none. | |
| 335 | + * | |
| 336 | + * @since 1.0.0 | |
| 337 | + * | |
| 338 | + * @return array | |
| 339 | + */ | |
| 340 | + public function get_suggested_donations_default() { | |
| 341 | + $value = get_post_meta( $this->post->ID, '_campaign_suggested_donations_default', true ); | |
| 342 | + | |
| 343 | + if ( is_array( $value ) && isset( $value[0] ) ) { | |
| 344 | + $value = $value[0]; | |
| 345 | + } | |
| 346 | + | |
| 347 | + return apply_filters( 'charitable_campaign_suggested_donations_default', $value, $this ); | |
| 348 | + } | |
| 349 | + | |
| 350 | + /** | |
| 257 | 351 | * Returns the end date in your preferred format. |
| 258 | 352 | * |
| 259 | 353 | * If a format is not provided, the user-defined date_format in WordPress settings is used. |
| 260 | 354 | * |
| @@ -286,8 +380,9 @@ | ||
| 286 | 380 | /** |
| 287 | 381 | * Returns the timetamp of the end date. |
| 288 | 382 | * |
| 289 | 383 | * @since 1.0.0 |
| 384 | + * @since 1.8.1.10 | |
| 290 | 385 | * |
| 291 | 386 | * @return int|false Int if campaign has an end date. False if campaign has no end date. |
| 292 | 387 | */ |
| 293 | 388 | public function get_end_time() { |
| @@ -302,8 +397,13 @@ | ||
| 302 | 397 | $date_time = explode( ' ', $end_date ); |
| 303 | 398 | $date = explode( '-', $date_time[0] ); |
| 304 | 399 | $time = explode( ':', $date_time[1] ); |
| 305 | 400 | |
| 401 | + // Check for undefined array keys. If any of these are not defined, return false. | |
| 402 | + if ( ! isset( $date[0] ) || ! isset( $date[1] ) || ! isset( $date[2] ) || ! isset( $time[0] ) || ! isset( $time[1] ) || ! isset( $time[2] ) ) { | |
| 403 | + return false; | |
| 404 | + } | |
| 405 | + | |
| 306 | 406 | $this->end_time = mktime( $time[0], $time[1], $time[2], $date[1], $date[2], $date[0] ); |
| 307 | 407 | } |
| 308 | 408 | |
| 309 | 409 | return $this->end_time; |
| @@ -360,9 +460,10 @@ | ||
| 360 | 460 | /* Condition 2: There is less than an hour left. */ |
| 361 | 461 | |
| 362 | 462 | $minutes_remaining = ceil( $seconds_left / 60 ); |
| 363 | 463 | $time_left = apply_filters( |
| 364 | - 'charitabile_campaign_minutes_left', | |
| 464 | + 'charitabile_campaign_minutes_left', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a legacy hook used by other Charitable code. Changing it would break existing functionality. | |
| 465 | + // translators: %s is the number of minutes left. | |
| 365 | 466 | sprintf( _n( '%s Minute Left', '%s Minutes Left', $minutes_remaining, 'charitable' ), '<span class="amount time-left minutes-left">' . $minutes_remaining . '</span>' ), |
| 366 | 467 | $this |
| 367 | 468 | ); |
| 368 | 469 | |
| @@ -371,9 +472,10 @@ | ||
| 371 | 472 | /* Condition 3: There is less than a day left. */ |
| 372 | 473 | |
| 373 | 474 | $hours_remaining = floor( $seconds_left / 3600 ); |
| 374 | 475 | $time_left = apply_filters( |
| 375 | - 'charitabile_campaign_hours_left', | |
| 476 | + 'charitabile_campaign_hours_left', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a legacy hook used by other Charitable code. Changing it would break existing functionality. | |
| 477 | + // translators: %s is the number of hours left. | |
| 376 | 478 | sprintf( _n( '%s Hour Left', '%s Hours Left', $hours_remaining, 'charitable' ), '<span class="amount time-left hours-left">' . $hours_remaining . '</span>' ), |
| 377 | 479 | $this |
| 378 | 480 | ); |
| 379 | 481 | |
| @@ -382,14 +484,15 @@ | ||
| 382 | 484 | /* Condition 4: There is more than a day left. */ |
| 383 | 485 | |
| 384 | 486 | $days_remaining = floor( $seconds_left / 86400 ); |
| 385 | 487 | $time_left = apply_filters( |
| 386 | - 'charitabile_campaign_days_left', | |
| 488 | + 'charitabile_campaign_days_left', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a legacy hook used by other Charitable code. Changing it would break existing functionality. | |
| 489 | + // translators: %s is the number of days left. | |
| 387 | 490 | sprintf( _n( '%s Day Left', '%s Days Left', $days_remaining, 'charitable' ), '<span class="amount time-left days-left">' . $days_remaining . '</span>' ), |
| 388 | 491 | $this |
| 389 | 492 | ); |
| 390 | 493 | |
| 391 | - }//end if | |
| 494 | + } //end if | |
| 392 | 495 | |
| 393 | 496 | return apply_filters( 'charitable_campaign_time_left', $time_left, $this ); |
| 394 | 497 | } |
| 395 | 498 | |
| @@ -437,12 +540,15 @@ | ||
| 437 | 540 | return ''; |
| 438 | 541 | } |
| 439 | 542 | |
| 440 | 543 | if ( ! $this->has_goal() ) { |
| 544 | + // translators: %s is the time since the campaign ended. | |
| 441 | 545 | $message = __( 'This campaign ended %s ago', 'charitable' ); |
| 442 | 546 | } elseif ( $this->has_achieved_goal() ) { |
| 547 | + // translators: %s is the time since the campaign ended. | |
| 443 | 548 | $message = __( 'This campaign successfully reached its funding goal and ended %s ago', 'charitable' ); |
| 444 | 549 | } else { |
| 550 | + // translators: %s is the time since the campaign ended. | |
| 445 | 551 | $message = __( 'This campaign failed to reach its funding goal %s ago', 'charitable' ); |
| 446 | 552 | } |
| 447 | 553 | |
| 448 | 554 | return apply_filters( 'charitable_campaign_finished_notice', sprintf( $message, '<span class="time-ago">' . human_time_diff( $this->get_end_time() ) . '</span>' ), $this ); |
| @@ -478,8 +584,24 @@ | ||
| 478 | 584 | return $this->goal; |
| 479 | 585 | } |
| 480 | 586 | |
| 481 | 587 | /** |
| 588 | + * Returns the minimum donation amount of the campaign. | |
| 589 | + * | |
| 590 | + * @since 1.0.0 | |
| 591 | + * | |
| 592 | + * @return string|false Amount if goal is set. False otherwise. | |
| 593 | + */ | |
| 594 | + public function get_min_donation_amount() { | |
| 595 | + if ( ! isset( $this->min_donation_amount ) ) { | |
| 596 | + $this->min_donation_amount = $this->get_meta( '_campaign_min_donation_amount' ) ? $this->get_meta( '_campaign_min_donation_amount' ) : false; | |
| 597 | + } | |
| 598 | + | |
| 599 | + return $this->min_donation_amount; | |
| 600 | + } | |
| 601 | + | |
| 602 | + | |
| 603 | + /** | |
| 482 | 604 | * Returns whether a goal has been set (anything greater than $0 is a goal). |
| 483 | 605 | * |
| 484 | 606 | * @since 1.0.0 |
| 485 | 607 | * |
| @@ -566,10 +688,11 @@ | ||
| 566 | 688 | * |
| 567 | 689 | * @return string |
| 568 | 690 | */ |
| 569 | 691 | public function get_status() { |
| 570 | - $status = $this->post->post_status; | |
| 571 | 692 | |
| 693 | + $status = $post_status = isset( $this->post ) && ! empty( $this->post->post_status ) ? $this->post->post_status : false; // phpcs:ignore | |
| 694 | + | |
| 572 | 695 | if ( 'publish' == $status ) { |
| 573 | 696 | $status = $this->has_ended() ? 'finished' : 'active'; |
| 574 | 697 | } |
| 575 | 698 | |
| @@ -581,9 +704,9 @@ | ||
| 581 | 704 | * @param string $status The campaign status. |
| 582 | 705 | * @param string $post_status The post_status of the campaign. |
| 583 | 706 | * @param Charitable_Campaign $campaign This instance of `Charitable_Campaign`. |
| 584 | 707 | */ |
| 585 | - return apply_filters( 'charitable_campaign_status', $status, $this->post->post_status, $this ); | |
| 708 | + return apply_filters( 'charitable_campaign_status', $status, $post_status, $this ); | |
| 586 | 709 | } |
| 587 | 710 | |
| 588 | 711 | /** |
| 589 | 712 | * Return a status key for the campaign. |
| @@ -639,8 +762,11 @@ | ||
| 639 | 762 | * Return the campaign status tag. |
| 640 | 763 | * |
| 641 | 764 | * @since 1.0.0 |
| 642 | 765 | * |
| 766 | + * @param boolean $show_achievement Whether to show the achievement status tag. | |
| 767 | + * @param boolean $show_active_tag Whether to show the active status tag. | |
| 768 | + * | |
| 643 | 769 | * @return string |
| 644 | 770 | */ |
| 645 | 771 | public function get_status_tag( $show_achievement = null, $show_active_tag = null ) { |
| 646 | 772 | $key = $this->get_status_key(); |
| @@ -821,8 +947,9 @@ | ||
| 821 | 947 | $goal = $this->get_meta( '_campaign_goal' ); |
| 822 | 948 | |
| 823 | 949 | if ( $goal ) { |
| 824 | 950 | $ret = sprintf( |
| 951 | + // translators: %1$s is the amount donated, %2$s is the goal amount. | |
| 825 | 952 | _x( '%1$s donated of %2$s goal', 'amount donated of goal', 'charitable' ), |
| 826 | 953 | '<span class="amount">' . $currency_helper->get_monetary_amount( $amount ) . '</span>', |
| 827 | 954 | '<span class="goal-amount">' . $currency_helper->get_monetary_amount( $goal ) . '</span>' |
| 828 | 955 | ); |
| @@ -827,8 +954,9 @@ | ||
| 827 | 954 | '<span class="goal-amount">' . $currency_helper->get_monetary_amount( $goal ) . '</span>' |
| 828 | 955 | ); |
| 829 | 956 | } else { |
| 830 | 957 | $ret = sprintf( |
| 958 | + // translators: %s is the amount donated. | |
| 831 | 959 | _x( '%s donated', 'amount donated', 'charitable' ), |
| 832 | 960 | '<span class="amount">' . $currency_helper->get_monetary_amount( $amount ) . '</span>' |
| 833 | 961 | ); |
| 834 | 962 | } |
| @@ -848,8 +976,39 @@ | ||
| 848 | 976 | return apply_filters( 'charitable_donation_summary', $ret, $this, $amount, $goal ); |
| 849 | 977 | } |
| 850 | 978 | |
| 851 | 979 | /** |
| 980 | + * Return a string especially for the campaign builder describing the campaign's donation summary. | |
| 981 | + * | |
| 982 | + * @since 1.8.0 | |
| 983 | + * | |
| 984 | + * @return string | |
| 985 | + */ | |
| 986 | + public function get_builder_donation_summary() { | |
| 987 | + $currency_helper = charitable_get_currency_helper(); | |
| 988 | + $amount = $this->get_donated_amount(); | |
| 989 | + $goal = $this->get_meta( '_campaign_goal' ); | |
| 990 | + | |
| 991 | + $ret = sprintf( | |
| 992 | + // translators: %s is the amount donated. | |
| 993 | + _x( '%s donated', 'amount donated', 'charitable' ), | |
| 994 | + '<span class="amount">' . $currency_helper->get_monetary_amount( $amount ) . '</span>' | |
| 995 | + ); | |
| 996 | + | |
| 997 | + /** | |
| 998 | + * Filter the returned string if you want to display the donation summary in a different way. | |
| 999 | + * | |
| 1000 | + * @since 1.8.0 | |
| 1001 | + * | |
| 1002 | + * @param string $ret The summary. | |
| 1003 | + * @param Charitable_Campaign $this This campaign object. | |
| 1004 | + * @param float $amount The amount donated. | |
| 1005 | + * @param int $goal The campaign goal. | |
| 1006 | + */ | |
| 1007 | + return apply_filters( 'charitable_builder_donation_summary', $ret, $this, $amount, $goal ); | |
| 1008 | + } | |
| 1009 | + | |
| 1010 | + /** | |
| 852 | 1011 | * Return the percentage donated. Use this if you want a formatted string. |
| 853 | 1012 | * |
| 854 | 1013 | * @since 1.0.0 |
| 855 | 1014 | * |
| @@ -869,10 +1028,43 @@ | ||
| 869 | 1028 | |
| 870 | 1029 | /** |
| 871 | 1030 | * Returns the percentage donated as a number. |
| 872 | 1031 | * |
| 873 | - * @since 1.0.0 | |
| 1032 | + * Ported from Charitable Pro, 1.8.13, THEN deliberately changed — | |
| 1033 | + * Lite deviates from Pro here on purpose. Pro's own version (and my | |
| 1034 | + * first port of it) unconditionally strips the thousands separator | |
| 1035 | + * and swaps the decimal separator, which is correct for a | |
| 1036 | + * DISPLAY-locale-formatted goal like "1,000.00" but wrong for the | |
| 1037 | + * CANONICAL dot-decimal form sanitize_campaign_goal() actually | |
| 1038 | + * stores: that method runs every saved goal through | |
| 1039 | + * Charitable_Currency::sanitize_monetary_amount(), which returns | |
| 1040 | + * floatval() — always dot-decimal, regardless of site locale. On a | |
| 1041 | + * comma-decimal / dot-thousands site (Settings > General; both | |
| 1042 | + * separators are user-settable), the unconditional strip reads an | |
| 1043 | + * already-canonical "1000.5" as "10005" — a campaign that raised 300 | |
| 1044 | + * of a 1000.5 goal reported 3% instead of 30%. | |
| 874 | 1045 | * |
| 1046 | + * is_numeric() distinguishes the two shapes: a canonical dot-decimal | |
| 1047 | + * value passes it and is used via a plain floatval(), untouched; a | |
| 1048 | + * display-formatted value ("1,000.00", "1.000,50") does not, and | |
| 1049 | + * still gets the separator treatment below. This is what makes both | |
| 1050 | + * the locale-formatted-input case (list-campaigns/get-campaign's own | |
| 1051 | + * "1,000.00" trap) and the canonical-storage case correct at once. | |
| 1052 | + * | |
| 1053 | + * Callers reach well beyond the abilities surface: the front-end | |
| 1054 | + * progress bar (templates/campaign/progress-bar.php, both the bar | |
| 1055 | + * width and the aria-valuenow a screen reader announces), the | |
| 1056 | + * dashboard and reports classes, and get_percent_donated() below all | |
| 1057 | + * go through this method. Pro carries the same bug on the same | |
| 1058 | + * front-end path; Charitable_Campaign_Abilities::percent_raised() | |
| 1059 | + * being correct would not have been enough to fix what a donor's | |
| 1060 | + * screen reader announces. The verbatim-fidelity rule for this port | |
| 1061 | + * covers the abilities registrars; it does not require shipping a | |
| 1062 | + * known locale regression on a shared front-end template. | |
| 1063 | + * | |
| 1064 | + * @since 1.0.0 | |
| 1065 | + * @version 1.8.13 | |
| 1066 | + * | |
| 875 | 1067 | * @return int |
| 876 | 1068 | */ |
| 877 | 1069 | public function get_percent_donated_raw() { |
| 878 | 1070 | if ( ! $this->has_goal() ) { |
| @@ -878,9 +1070,30 @@ | ||
| 878 | 1070 | if ( ! $this->has_goal() ) { |
| 879 | 1071 | return false; |
| 880 | 1072 | } |
| 881 | 1073 | |
| 882 | - return ( $this->get_donated_amount( true ) / $this->get_goal() ) * 100; | |
| 1074 | + $donated = floatval( $this->get_donated_amount( true ) ); | |
| 1075 | + | |
| 1076 | + $goal_meta = $this->get_meta( '_campaign_goal' ); | |
| 1077 | + $currency = Charitable_Currency::get_instance(); | |
| 1078 | + $decimal_separator = $currency->get_decimal_separator(); | |
| 1079 | + $thousands_separator = $currency->get_thousands_separator(); | |
| 1080 | + | |
| 1081 | + /* | |
| 1082 | + * Already-canonical dot-decimal values (the shape every saved | |
| 1083 | + * goal is actually stored in) pass through untouched; anything | |
| 1084 | + * else is a display-formatted string and gets the thousands | |
| 1085 | + * strip + decimal-separator swap. | |
| 1086 | + */ | |
| 1087 | + $goal = is_numeric( $goal_meta ) | |
| 1088 | + ? floatval( $goal_meta ) | |
| 1089 | + : floatval( str_replace( $decimal_separator, '.', str_replace( $thousands_separator, '', $goal_meta ) ) ); | |
| 1090 | + | |
| 1091 | + if ( $goal <= 0 ) { | |
| 1092 | + return 0; | |
| 1093 | + } | |
| 1094 | + | |
| 1095 | + return ( $donated / $goal ) * 100; | |
| 883 | 1096 | } |
| 884 | 1097 | |
| 885 | 1098 | /** |
| 886 | 1099 | * Return the number of people who have donated to the campaign. |
| @@ -905,8 +1118,36 @@ | ||
| 905 | 1118 | ); |
| 906 | 1119 | } |
| 907 | 1120 | |
| 908 | 1121 | /** |
| 1122 | + * Return the number of donations (transactions) for the campaign. | |
| 1123 | + * | |
| 1124 | + * Ported from Charitable Pro unchanged. Backs the | |
| 1125 | + * charitable/get-campaign-performance ability's donation_count field — | |
| 1126 | + * a campaign with two donations from the same donor has a donor_count | |
| 1127 | + * of 1 but a get_donation_count() of 2. | |
| 1128 | + * | |
| 1129 | + * @since 1.8.13 | |
| 1130 | + * | |
| 1131 | + * @return int | |
| 1132 | + */ | |
| 1133 | + public function get_donation_count() { | |
| 1134 | + /** | |
| 1135 | + * Filter the number of donations (transactions) for the campaign. | |
| 1136 | + * | |
| 1137 | + * @since 1.8.13 | |
| 1138 | + * | |
| 1139 | + * @param int $count Number of donations. | |
| 1140 | + * @param Charitable_Campaign $this This campaign object. | |
| 1141 | + */ | |
| 1142 | + return apply_filters( | |
| 1143 | + 'charitable_campaign_donation_count', | |
| 1144 | + (int) charitable_get_table( 'campaign_donations' )->count_campaign_donations( $this->ID ), | |
| 1145 | + $this | |
| 1146 | + ); | |
| 1147 | + } | |
| 1148 | + | |
| 1149 | + /** | |
| 909 | 1150 | * Returns the donation form object. |
| 910 | 1151 | * |
| 911 | 1152 | * @since 1.0.0 |
| 912 | 1153 | * |
| @@ -1138,8 +1379,24 @@ | ||
| 1138 | 1379 | return charitable_get_currency_helper()->sanitize_monetary_amount( $value ); |
| 1139 | 1380 | } |
| 1140 | 1381 | |
| 1141 | 1382 | /** |
| 1383 | + * Sanitize the campaign minimum amount. | |
| 1384 | + * | |
| 1385 | + * @since 1.7.0.3 | |
| 1386 | + * | |
| 1387 | + * @param string $value Current value of min amount. | |
| 1388 | + * @return string|int | |
| 1389 | + */ | |
| 1390 | + public static function sanitize_min_donation_amount( $value ) { | |
| 1391 | + if ( empty( $value ) || ! $value ) { | |
| 1392 | + return 0; | |
| 1393 | + } | |
| 1394 | + | |
| 1395 | + return charitable_get_currency_helper()->sanitize_monetary_amount( $value ); | |
| 1396 | + } | |
| 1397 | + | |
| 1398 | + /** | |
| 1142 | 1399 | * Sanitize the campaign end date. |
| 1143 | 1400 | * |
| 1144 | 1401 | * We use WP_Locale to parse the month that the user has set. |
| 1145 | 1402 | * |
| @@ -1174,9 +1431,24 @@ | ||
| 1174 | 1431 | * |
| 1175 | 1432 | * @param array $value Current suggested donations value. |
| 1176 | 1433 | * @return array |
| 1177 | 1434 | */ |
| 1178 | - public static function sanitize_campaign_suggested_donations( $value ) { | |
| 1435 | + public static function sanitize_campaign_suggested_donations_default( $value ) { | |
| 1436 | + | |
| 1437 | + return $value; // this is likely an array. | |
| 1438 | + } | |
| 1439 | + | |
| 1440 | + /** | |
| 1441 | + * Sanitize the campaign suggested donations. | |
| 1442 | + * | |
| 1443 | + * @since 1.0.0 | |
| 1444 | + * | |
| 1445 | + * @param array $value Current suggested donations value. | |
| 1446 | + * @param bool $submitted Whether the value was submitted. | |
| 1447 | + * @param int $campaign_id The campaign ID. | |
| 1448 | + * @return array | |
| 1449 | + */ | |
| 1450 | + public static function sanitize_campaign_suggested_donations( $value, $submitted = false, $campaign_id = false ) { // phpcs:ignore | |
| 1179 | 1451 | if ( ! is_array( $value ) ) { |
| 1180 | 1452 | return array(); |
| 1181 | 1453 | } |
| 1182 | 1454 | |
| @@ -1188,8 +1460,18 @@ | ||
| 1188 | 1460 | |
| 1189 | 1461 | foreach ( $value as $key => $suggestion ) { |
| 1190 | 1462 | $value[ $key ]['amount'] = charitable_sanitize_amount( (string) $suggestion['amount'] ); |
| 1191 | 1463 | |
| 1464 | + /* | |
| 1465 | + * A description is OPTIONAL. filter_suggested_donation() above keeps any entry that has | |
| 1466 | + * a non-empty 'amount', so array( 'amount' => 25 ) is a perfectly valid suggestion with | |
| 1467 | + * no 'description' key at all - which is the normal shape when a campaign offers bare | |
| 1468 | + * amounts. Reading it unguarded emitted an "Undefined array key" warning on every such | |
| 1469 | + * sanitize pass. Defaulted rather than skipped so the key is always present in the | |
| 1470 | + * returned array, which is what callers and the stored meta already assume. | |
| 1471 | + */ | |
| 1472 | + $description = isset( $suggestion['description'] ) ? $suggestion['description'] : ''; | |
| 1473 | + | |
| 1192 | 1474 | /** |
| 1193 | 1475 | * Sanitize the description field. |
| 1194 | 1476 | * |
| 1195 | 1477 | * @since 1.6.51 |
| @@ -1198,11 +1480,11 @@ | ||
| 1198 | 1480 | * Returning false will allow HTML to be used, though it will |
| 1199 | 1481 | * still be filtered using wp_kses_post. |
| 1200 | 1482 | */ |
| 1201 | 1483 | if ( apply_filters( 'charitable_sanitize_suggested_amount_description', true ) ) { |
| 1202 | - $value[ $key ]['description'] = sanitize_text_field( $suggestion['description'] ); | |
| 1484 | + $value[ $key ]['description'] = sanitize_text_field( $description ); | |
| 1203 | 1485 | } else { |
| 1204 | - $value[ $key ]['description'] = wp_kses_post( $suggestion['description'] ); | |
| 1486 | + $value[ $key ]['description'] = wp_kses_post( $description ); | |
| 1205 | 1487 | } |
| 1206 | 1488 | } |
| 1207 | 1489 | |
| 1208 | 1490 | return $value; |
| @@ -1238,21 +1520,96 @@ | ||
| 1238 | 1520 | |
| 1239 | 1521 | /** |
| 1240 | 1522 | * Sanitize the campaign description. |
| 1241 | 1523 | * |
| 1242 | - * @since 1.0.0 | |
| 1524 | + * @since 1.0.0 | |
| 1525 | + * @updated 1.7.0.4 | |
| 1243 | 1526 | * |
| 1244 | 1527 | * @param string $value Current description value. |
| 1245 | 1528 | * @return string |
| 1246 | 1529 | */ |
| 1247 | 1530 | public static function sanitize_campaign_description( $value ) { |
| 1248 | - return sanitize_text_field( $value ); | |
| 1531 | + | |
| 1532 | + /** | |
| 1533 | + * Allowed HTML in description. | |
| 1534 | + */ | |
| 1535 | + $wp_kses_allowed_html = array( | |
| 1536 | + 'a' => array( | |
| 1537 | + 'href' => array(), | |
| 1538 | + 'target' => array(), | |
| 1539 | + 'class' => array(), | |
| 1540 | + 'title' => array(), | |
| 1541 | + ), | |
| 1542 | + 'br' => array(), | |
| 1543 | + 'img' => array( | |
| 1544 | + 'src' => array(), | |
| 1545 | + 'class' => array(), | |
| 1546 | + 'alt' => array(), | |
| 1547 | + ), | |
| 1548 | + 'h1' => array( | |
| 1549 | + 'class' => array(), | |
| 1550 | + ), | |
| 1551 | + 'h2' => array( | |
| 1552 | + 'class' => array(), | |
| 1553 | + ), | |
| 1554 | + 'h3' => array( | |
| 1555 | + 'class' => array(), | |
| 1556 | + ), | |
| 1557 | + 'h4' => array( | |
| 1558 | + 'class' => array(), | |
| 1559 | + ), | |
| 1560 | + 'h5' => array( | |
| 1561 | + 'class' => array(), | |
| 1562 | + ), | |
| 1563 | + 'h6' => array( | |
| 1564 | + 'class' => array(), | |
| 1565 | + ), | |
| 1566 | + 'div' => array( | |
| 1567 | + 'class' => array(), | |
| 1568 | + ), | |
| 1569 | + 'p' => array( | |
| 1570 | + 'style' => array(), | |
| 1571 | + 'class' => array(), | |
| 1572 | + ), | |
| 1573 | + 'ul' => array( | |
| 1574 | + 'class' => array(), | |
| 1575 | + ), | |
| 1576 | + 'li' => array( | |
| 1577 | + 'id' => array(), | |
| 1578 | + 'class' => array(), | |
| 1579 | + ), | |
| 1580 | + 'em' => array(), | |
| 1581 | + 'span' => array( | |
| 1582 | + 'class' => array(), | |
| 1583 | + ), | |
| 1584 | + 'strong' => array(), | |
| 1585 | + ); | |
| 1586 | + | |
| 1587 | + if ( apply_filters( 'charitable_sanitize_campaign_description', false ) ) { | |
| 1588 | + return sanitize_text_field( $value ); | |
| 1589 | + } else { | |
| 1590 | + return wp_kses( apply_filters( 'charitable_campaign_description', $value ), apply_filters( 'charitable_sanitize_campaign_description_allowed_html', $wp_kses_allowed_html ) ); | |
| 1591 | + } | |
| 1249 | 1592 | } |
| 1250 | 1593 | |
| 1251 | 1594 | /** |
| 1595 | + * Sanitize the campaign donation button text.. | |
| 1596 | + * | |
| 1597 | + * @since 1.7.0.9 | |
| 1598 | + * | |
| 1599 | + * @param string $value Current text value. | |
| 1600 | + * @return string | |
| 1601 | + */ | |
| 1602 | + public static function sanitize_campaign_donate_button_text( $value = false ) { | |
| 1603 | + | |
| 1604 | + return wp_strip_all_tags( $value ); | |
| 1605 | + } | |
| 1606 | + | |
| 1607 | + /** | |
| 1252 | 1608 | * Sanitize the value provided for custom donations. |
| 1253 | 1609 | * |
| 1254 | - * @since 1.3.6 | |
| 1610 | + * @since 1.3.6 | |
| 1611 | + * @version 1.8.0 | |
| 1255 | 1612 | * |
| 1256 | 1613 | * @param mixed $value Current custom donations value. |
| 1257 | 1614 | * @param array $submitted All posted values. |
| 1258 | 1615 | * @return boolean |
| @@ -1275,8 +1632,13 @@ | ||
| 1275 | 1632 | * @param array $submitted The submitted values. |
| 1276 | 1633 | */ |
| 1277 | 1634 | if ( apply_filters( 'charitable_campaign_permitted_without_custom_or_suggested', false, $submitted ) ) { |
| 1278 | 1635 | return $checked; |
| 1636 | + } | |
| 1637 | + | |
| 1638 | + // see if literally "0" was passed. | |
| 1639 | + if ( is_string( $value ) && '0' === $value ) { | |
| 1640 | + return 0; | |
| 1279 | 1641 | } |
| 1280 | 1642 | |
| 1281 | 1643 | /* If suggested donations are not set, custom donations needs to be enabled. */ |
| 1282 | 1644 | if ( ! isset( $submitted['_campaign_suggested_donations'] ) ) { |