PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
← All changes | assets/js/charitable.js +369 -126 1.6.7 → 1.8.13 View file →
@@ -12,16 +12,23 @@
12 12
13 13 /**
14 14 * Array of errors.
15 15 *
16 - * @access public
16 + * @access public
17 17 */
18 18 this.errors = [];
19 19
20 20 /**
21 + * Pending processes array.
22 + *
23 + * @access public
24 + */
25 + this.pending_processes = [];
26 +
27 + /**
21 28 * Form object.
22 29 *
23 - * @access public
30 + * @access public
24 31 */
25 32 this.form = form;
26 33
27 34 /**
@@ -26,9 +33,9 @@
26 33
27 34 /**
28 35 * Flag to allow processing to be paused (i.e. while something like Stripe is processing).
29 36 *
30 - * @access public
37 + * @access public
31 38 */
32 39 this.pause_processing = false;
33 40
34 41 /**
@@ -33,19 +40,44 @@
33 40
34 41 /**
35 42 * Flag to prevent the on_submit handler from sending multiple concurrent AJAX requests
36 43 *
37 - * @access public
44 + * @access public
38 45 */
39 46 this.submit_processing = false;
40 -
47 +
48 + /**
49 + * The donation amount.
50 + *
51 + * @access public
52 + */
53 + this.total = 0;
54 +
55 + /**
56 + * Whether to scroll to the top of the form after a submission with errors.
57 + *
58 + * @access private
59 + */
60 + this.prevent_scroll_to_top = false;
61 +
62 + /**
63 + * Reference to this object.
64 + *
65 + * @access private
66 + */
41 67 var self = this;
68 +
69 + /**
70 + * Body element reference.
71 + *
72 + * @access private
73 + */
42 74 var $body = $( 'body' );
43 75
44 76 /**
45 77 * Handle click of terms link.
46 78 *
47 - * @access private
79 + * @access private
48 80 */
49 81 var on_click_terms = function() {
50 82 self.form.find( '.charitable-terms-text' ).addClass( 'active' );
51 83 return false;
@@ -53,29 +85,41 @@
53 85
54 86 /**
55 87 * Focus event handler on custom donation amount input field.
56 88 *
57 - * @access private
89 + * @access private
58 90 */
59 91 var on_focus_custom_amount = function() {
60 -
61 92 $( this ).closest( 'li' ).trigger( 'click' ).find( 'input[name=donation_amount]' ).prop( 'checked', true ).trigger( 'change' );
62 93
63 94 self.form.off( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
64 95
65 96 $( this ).focus();
66 -
97 +
67 98 self.form.on( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
68 -
69 99 };
70 100
71 101 /**
102 + * Trigger jQuery events to broadcast the change in donation amount.
103 + *
104 + * @since 1.6.19
105 + *
106 + * @access private
107 + */
108 + var trigger_amount_change_events = function() {
109 + /* The chosen donation amount has changed. */
110 + $body.trigger( 'charitable:form:amount:changed', self );
111 +
112 + /* The overall donation amount has changed. */
113 + $body.trigger( 'charitable:form:total:changed', self );
114 + };
115 +
116 + /**
72 117 * Focus event handler for changes to custom donation amount input field.
73 118 *
74 - * @access private
119 + * @access private
75 120 */
76 121 var on_change_custom_donation_amount = function() {
77 -
78 122 var unformatted = self.unformat_amount( $( this ).val() );
79 123
80 124 if ( $.trim( unformatted ) > 0 ) {
81 125 $( this ).val( self.format_amount( unformatted ) );
@@ -80,8 +124,9 @@
80 124 if ( $.trim( unformatted ) > 0 ) {
81 125 $( this ).val( self.format_amount( unformatted ) );
82 126 }
83 127
128 + trigger_amount_change_events();
84 129 };
85 130
86 131 /**
87 132 * Select a donation amount.
@@ -87,10 +132,9 @@
87 132 * Select a donation amount.
88 133 *
89 134 * @return void
90 135 */
91 - var on_select_donation_amount = function() {
92 -
136 + var on_select_donation_amount = function() {
93 137 var $li = $( this ).closest( 'li' );
94 138
95 139 // Already selected, quit early to prevent focus/change loop
96 140 if ( $li.hasClass( 'selected' ) ) {
@@ -97,36 +141,50 @@
97 141 return;
98 142 }
99 143
100 144 $li.parents( '.charitable-donation-form' ).find( '.donation-amount.selected' ).removeClass( 'selected' );
101 -
102 - $li.addClass( 'selected' );
103 145
146 + $li.addClass( 'selected' ).find( '[name=donation_amount]' ).prop( 'checked', true );
147 +
104 148 if ( $li.hasClass( 'custom-donation-amount' ) ) {
105 149 $li.find( 'input.custom-donation-input' ).focus();
106 150 }
151 +
152 + trigger_amount_change_events();
107 153 };
108 154
109 155 /**
110 156 * Change event handler for payment gateway selector.
111 157 *
112 - * @access private
158 + * @access private
113 159 */
114 160 var on_change_payment_gateway = function() {
115 -
161 + var selectedGateway = $(this).val();
162 +
116 163 self.hide_inactive_payment_methods();
164 + self.show_active_payment_methods( selectedGateway );
117 165
118 - self.show_active_payment_methods( $(this).val() );
119 -
120 - };
166 + // Trigger resize event for Square gateway to fix iframe height issue
167 + if ( selectedGateway === 'square' || selectedGateway === 'square_core' ) {
168 + setTimeout(function() {
169 + window.dispatchEvent(new Event('resize'));
170 + }, 100);
171 + }
172 + };
121 173
122 174 /**
175 + * Event handler when the "Change" link is clicked for pre-filled donations.
176 + */
177 + var on_click_change_amount_link = function() {
178 + $(this).parent().addClass( 'charitable-hidden' );
179 + };
180 +
181 + /**
123 182 * Submit event handler for donation form.
124 183 *
125 - * @access private
184 + * @access private
126 185 */
127 186 var on_submit = function( event ) {
128 -
129 187 var helper = event.data.helper;
130 188
131 189 if ( helper.submit_processing ) {
132 190 return false;
@@ -135,20 +193,13 @@
135 193 helper.submit_processing = true;
136 194
137 195 /* Display the processing spinner and hide the button */
138 196 helper.show_processing();
139 -
197 +
140 198 /* Validate the form submission before going further. */
141 199 if ( false === helper.validate() ) {
142 -
143 - helper.hide_processing();
144 -
145 - helper.print_errors();
146 -
147 - helper.scroll_to_top();
148 -
200 + helper.cancel_processing();
149 201 return false;
150 -
151 202 }
152 203
153 204 /* If processing has been paused, return false now. */
154 205 if ( false !== helper.pause_processing ) {
@@ -159,16 +210,37 @@
159 210 if ( 1 !== helper.form.data( 'use-ajax' ) ) {
160 211 return true;
161 212 }
162 213
163 - /* If we're still here, trigger the processing event. */
164 - $body.trigger( 'charitable:form:process', helper );
214 + /* Continue on to process the donation. */
215 + maybe_process( helper, function() {
216 + $body.trigger( 'charitable:form:process', helper );
217 + } );
165 218
166 219 return false;
167 -
168 220 };
169 221
170 222 /**
223 + * Check whether there are any asynchronous threads we need to wait for.
224 + *
225 + * If not, trigger processing. If there are, check again in 500ms.
226 + */
227 + var maybe_process = function( helper, callback ) {
228 + if ( ! helper.waiting() ) {
229 + /* Double-check that there are still no errors. */
230 + if ( helper.get_errors().length > 0 ) {
231 + helper.cancel_processing();
232 + } else {
233 + callback();
234 + }
235 +
236 + return;
237 + }
238 +
239 + setTimeout( maybe_process, 500, helper, callback );
240 + }
241 +
242 + /**
171 243 * Process the donation.
172 244 *
173 245 * This is a callback for the 'charitable:form:process' event. It's called
174 246 * after validation has taken place.
@@ -177,14 +249,15 @@
177 249 * @param object Donation_Form
178 250 */
179 251 var process_donation = function( event, helper ) {
180 252
181 - var data = helper.get_data();
253 + var data = helper.get_data();
254 +
182 255 var form = helper.form;
183 256
184 - /* Cancel the default Charitable action, but pass it along as the form_action variable */
257 + /* Cancel the default Charitable action, but pass it along as the form_action variable */
185 258 data.action = 'make_donation';
186 - data.form_action = data.charitable_action;
259 + data.form_action = data.charitable_action;
187 260 delete data.charitable_action;
188 261
189 262 $.ajax({
190 263 type: "POST",
@@ -195,41 +268,32 @@
195 268 xhrFields: {
196 269 withCredentials: true
197 270 },
198 271 success: function (response) {
272 + $body.trigger( 'charitable:form:processed', [ response, helper ] );
199 273
200 274 if ( response.success ) {
201 - window.location.href = response.redirect_to;
275 + maybe_process( helper, function() {
276 + window.location.href = response.redirect_to;
277 + } );
202 278 }
203 279 else {
204 - helper.hide_processing();
280 + helper.cancel_processing( response.errors );
205 281
206 - helper.print_errors( response.errors );
207 -
208 - helper.scroll_to_top();
209 -
210 282 if ( response.donation_id ) {
211 283 helper.set_donation_id( response.donation_id );
212 284 }
213 285 }
214 286 }
215 -
216 287 }).fail(function (response, textStatus, errorThrown) {
217 -
218 288 if ( window.console && window.console.log ) {
219 289 console.log( response );
220 290 }
221 291
222 - helper.hide_processing();
223 -
224 - helper.print_errors( [ CHARITABLE_VARS.error_unknown ] );
225 -
226 - helper.scroll_to_top();
227 -
292 + helper.cancel_processing( [ CHARITABLE_VARS.error_unknown ] );
228 293 });
229 294
230 295 return false;
231 -
232 296 }
233 297
234 298 /**
235 299 * Set up event handlers for donation forms.
@@ -236,9 +300,8 @@
236 300 *
237 301 * @return void
238 302 */
239 303 var init = function() {
240 -
241 304 // Init donation amount selection
242 305 self.form.on( 'click', '.donation-amount', on_select_donation_amount );
243 306 self.form.on( 'focus', 'input.custom-donation-input', on_focus_custom_amount );
244 307 self.form.on( 'click', '.charitable-terms-link', on_click_terms );
@@ -254,8 +317,10 @@
254 317 self.hide_inactive_payment_methods();
255 318 self.form.on( 'change', '#charitable-gateway-selector input[name=gateway]', on_change_payment_gateway );
256 319 }
257 320
321 + self.form.on( 'click', '.change-donation', on_click_change_amount_link );
322 +
258 323 // Handle donation form submission
259 324 self.form.on( 'submit', {
260 325 helper : self,
261 326 }, on_submit );
@@ -264,11 +329,11 @@
264 329 if ( false === CHARITABLE.forms_initialized ) {
265 330
266 331 // Process the donation on the 'charitable:form:process' event.
267 332 $body.on( 'charitable:form:process', process_donation );
268 -
333 +
269 334 $body.trigger( 'charitable:form:initialize', self );
270 -
335 +
271 336 CHARITABLE.forms_initialized = true;
272 337 }
273 338
274 339 $body.trigger( 'charitable:form:loaded', self );
@@ -274,9 +339,8 @@
274 339 $body.trigger( 'charitable:form:loaded', self );
275 340 }
276 341
277 342 init();
278 -
279 343 };
280 344
281 345 /**
282 346 * Return a particular input field.
@@ -297,8 +361,21 @@
297 361 return this.form.find( '[name=email]' ).val();
298 362 };
299 363
300 364 /**
365 + * Returns whether this is a recurring donation.
366 + *
367 + * @since 1.4.21
368 + *
369 + * @return boolean
370 + */
371 + Donation_Form.prototype.is_recurring_donation = function() {
372 + var recurring = this.form.find( '[name=recurring_donation]:checked, [name=recurring_donation][type=hidden]' );
373 +
374 + return recurring.length && 'once' !== recurring.val();
375 + };
376 +
377 + /**
301 378 * Get the chosen suggested amount.
302 379 *
303 380 * Note that when no option has been selected, or a custom donation amount has been added,
304 381 * this will return 0.
@@ -323,12 +400,12 @@
323 400 *
324 401 * @return float
325 402 */
326 403 Donation_Form.prototype.get_custom_amount = function() {
327 - var input = this.form.find( '.charitable-donation-options.active .custom-donation-input' );
404 + var input = this.form.find( '.charitable-donation-options.active input.custom-donation-input,.charitable-donation-options.active input.custom-donation-amount' );
328 405
329 406 if ( 0 === input.length ) {
330 - input = this.form.find( '.custom-donation-input' );
407 + input = this.form.find( 'input.custom-donation-input' );
331 408 }
332 409
333 410 return accounting.unformat(
334 411 input.val(),
@@ -336,17 +413,59 @@
336 413 );
337 414 }
338 415
339 416 /**
417 + * Get the donation subtotal, which is the amount passed by the donor.
418 + *
419 + * Notably, this does not include any additional amounts that may be added onto the donation
420 + * total, such as processing fees that the donor agrees to pay.
421 + *
422 + * @since 1.6.7
423 + *
424 + * @return float
425 + */
426 + Donation_Form.prototype.get_subtotal = function() {
427 + return this.get_suggested_amount() || this.get_custom_amount();
428 + };
429 +
430 + /**
340 431 * Get the submitted amount, taking into account both the custom & suggested donation fields.
341 432 *
342 433 * @return float
343 434 */
344 435 Donation_Form.prototype.get_amount = function() {
345 - return this.get_suggested_amount() || this.get_custom_amount();
436 + this.total = this.get_subtotal();
437 +
438 + this.form.trigger( 'charitable:form:get_amount', this );
439 +
440 + return this.total;
346 441 };
347 442
348 443 /**
444 + * Get the submitted amount, taking into account both the custom & suggested donation fields.
445 + *
446 + * @since 1.6.8
447 + *
448 + * @param float add
449 + * @return void
450 + */
451 + Donation_Form.prototype.add_amount = function( add ) {
452 + this.total += add;
453 + };
454 +
455 + /**
456 + * Get the submitted amount, taking into account both the custom & suggested donation fields.
457 + *
458 + * @since 1.6.8
459 + *
460 + * @param float remove Amount to be removed
461 + * @return void
462 + */
463 + Donation_Form.prototype.remove_amount = function( remove ) {
464 + this.total -= remove;
465 + };
466 +
467 + /**
349 468 * Get a description of the donation.
350 469 *
351 470 * @return string
352 471 */
@@ -393,9 +512,9 @@
393 512 /**
394 513 * Clear credit card fields.
395 514
396 515 *
397 - * This is used by gateways that create tokens through Javascript (such as Stripe), to
516 + * This is used by gateways that create tokens through Javascript (such as Stripe), to
398 517 * avoid credit card details hitting the server.
399 518 *
400 519 * @return void
401 520 */
@@ -441,12 +560,12 @@
441 560 */
442 561 Donation_Form.prototype.show_active_payment_methods = function( active ) {
443 562 var active = active || this.get_payment_method();
444 563 var fields = this.form.find( '.charitable-gateway-fields[data-gateway=' + active + ']' );
445 -
564 +
446 565 fields.show();
447 566 fields.find( '[data-required]' ).attr( 'required', true );
448 - };
567 + };
449 568
450 569 /**
451 570 * Select a donation amount.
452 571 *
@@ -462,20 +581,46 @@
462 581 symbol : symbol,
463 582 decimal : CHARITABLE_VARS.currency_format_decimal_sep,
464 583 thousand: CHARITABLE_VARS.currency_format_thousand_sep,
465 584 precision : CHARITABLE_VARS.currency_format_num_decimals,
466 - format: CHARITABLE_VARS.currency_format
585 + format: CHARITABLE_VARS.currency_format
467 586 }).trim();
468 587 };
469 588
470 589 /**
471 - * Select a donation amount.
590 + * Unformat a currency string to a plain float.
472 591 *
473 - * @param int price
474 - * @return string
592 + * Handles the case where the site uses a non-period decimal separator (e.g. comma)
593 + * but the user types a period as the decimal point — a common mistake on non-US
594 + * keyboards. accounting.unformat() would otherwise strip the period as if it were
595 + * a thousands separator, turning "5.33" into 533.
596 + *
597 + * Heuristic: if the configured decimal is not a period, the value contains a period,
598 + * and the value does NOT already contain the configured decimal separator:
599 + * - One period followed by 1–2 digits at end → treat as decimal (e.g. "5.33" → "5,33")
600 + * - One period followed by exactly 3 digits at end → treat as thousands (e.g. "1.000" → 1000)
601 + * - Multiple periods → all thousands separators; let accounting.unformat strip them
602 + *
603 + * @param string|number price
604 + * @return float
475 605 */
476 606 Donation_Form.prototype.unformat_amount = function( price ) {
477 - return Math.abs( parseFloat( accounting.unformat( price, CHARITABLE_VARS.currency_format_decimal_sep ) ) );
607 + var decimal = CHARITABLE_VARS.currency_format_decimal_sep;
608 +
609 + price = String( price );
610 +
611 + if ( decimal !== '.' && price.indexOf( '.' ) !== -1 && price.indexOf( decimal ) === -1 ) {
612 + var periodCount = ( price.match( /\./g ) || [] ).length;
613 +
614 + if ( periodCount === 1 && ! /\.\d{3}$/.test( price ) ) {
615 + // Single period not followed by exactly 3 digits — treat as decimal separator.
616 + price = price.replace( '.', decimal );
617 + }
618 + // Single period followed by 3 digits (thousands) or multiple periods:
619 + // fall through and let accounting.unformat strip them.
620 + }
621 +
622 + return Math.abs( parseFloat( accounting.unformat( price, decimal ) ) );
478 623 };
479 624
480 625 /**
481 626 * Add an error message.
@@ -507,16 +652,16 @@
507 652
508 653 /**
509 654 * Print the errors at the top of the form.
510 655 *
511 - * @param array
656 + * @param array
512 657 */
513 658 Donation_Form.prototype.print_errors = function( errors ) {
514 -
515 659 var e = errors || this.errors,
516 660 i = 0,
517 661 count = e.length,
518 - output = '';
662 + output = '',
663 + error_notice_donation_display = typeof CHARITABLE_VARS.error_notice_donation_display === 'undefined' ? 'top' : CHARITABLE_VARS.error_notice_donation_display;
519 664
520 665 if ( 0 === count ) {
521 666 return;
522 667 }
@@ -525,12 +670,16 @@
525 670 this.form.find( '.charitable-form-errors' ).remove();
526 671 }
527 672
528 673 output += '<div class="charitable-form-errors charitable-notice"><ul class="errors"><li>';
529 - output += e.join( '</li><li>' );
674 + output += e.join( '</li><li>' );
530 675 output += '</li></ul></div>';
531 676
532 - this.form.prepend( output );
677 + if ( error_notice_donation_display === 'bottom' ) {
678 + this.form.append( output );
679 + } else {
680 + this.form.prepend( output );
681 + }
533 682
534 683 }
535 684
536 685 /**
@@ -536,18 +685,65 @@
536 685 /**
537 686 * Clear the errors and remove the printed errors.
538 687 */
539 688 Donation_Form.prototype.clear_errors = function() {
689 + this.errors = [];
540 690
541 - this.errors = [];
542 -
543 691 if ( this.form.find( '.charitable-form-errors' ).length ) {
544 692 this.form.find( '.charitable-form-errors' ).remove();
545 693 }
694 + }
546 695
696 + /**
697 + * Return whether we are waiting for an asynchronous process to finish.
698 + *
699 + * @since 1.6.9
700 + *
701 + * @return boolean
702 + */
703 + Donation_Form.prototype.waiting = function() {
704 + return this.pending_processes.length > 0;
547 705 }
548 706
549 707 /**
708 + * Add a pending process.
709 + *
710 + * @since 1.6.9
711 + *
712 + * @param string Process identifier.
713 + * @return int Index of the process.
714 + */
715 + Donation_Form.prototype.add_pending_process = function( process ) {
716 + var index = this.pending_processes.indexOf( process );
717 + return -1 === index ? ( this.pending_processes.push( process ) - 1 ) : index;
718 + }
719 +
720 + /**
721 + * Remove a pending process by index.
722 + *
723 + * @since 1.6.9
724 + *
725 + * @param int Index of the process.
726 + * @return void
727 + */
728 + Donation_Form.prototype.remove_pending_process = function( index ) {
729 + this.pending_processes.splice( index, 1 );
730 + }
731 +
732 + /**
733 + * Remove a pending process by process name.
734 + *
735 + * @since 1.6.17
736 + *
737 + * @param string Name of the process.
738 + * @return void
739 + */
740 + Donation_Form.prototype.remove_pending_process_by_name = function( process ) {
741 + var index = this.pending_processes.indexOf( process );
742 + return -1 !== index && this.remove_pending_process( index );
743 + }
744 +
745 + /**
550 746 * Show that the donation form is processing.
551 747 */
552 748 Donation_Form.prototype.show_processing = function() {
553 749 this.form.find( '.charitable-form-processing' ).show();
@@ -568,8 +764,12 @@
568 764 /**
569 765 * Scroll to the top of the form.
570 766 */
571 767 Donation_Form.prototype.scroll_to_top = function() {
768 + if ( this.prevent_scroll_to_top ) {
769 + this.prevent_scroll_to_top = false;
770 + return;
771 + }
572 772
573 773 var $modal = this.form.parents( '.charitable-modal' );
574 774
575 775 if ( $modal.length ) {
@@ -575,11 +775,26 @@
575 775 if ( $modal.length ) {
576 776 $modal.scrollTop( 0 );
577 777 }
578 778 else {
579 - window.scrollTo( this.form.position().left, this.form.position().top );
580 - }
779 + window.scrollTo( this.form.offset().left, this.form.offset().top );
780 + }
781 + };
581 782
783 + /**
784 + * Cancel donation processing.
785 + *
786 + * @param array errors Error messages to show.
787 + */
788 + Donation_Form.prototype.cancel_processing = function( errors ) {
789 + this.hide_processing();
790 + this.print_errors( errors );
791 + var error_notice_donation_display = typeof CHARITABLE_VARS.error_notice_donation_display === 'undefined' ? 'top' : CHARITABLE_VARS.error_notice_donation_display;
792 + if ( '' === error_notice_donation_display || 'top' === error_notice_donation_display ) {
793 + this.scroll_to_top();
794 + }
795 +
796 + this.form.trigger( 'charitable:form:cancelled', this );
582 797 };
583 798
584 799 /**
585 800 * Set the donation ID.
@@ -593,9 +808,8 @@
593 808 *
594 809 * @return object
595 810 */
596 811 Donation_Form.prototype.get_data = function() {
597 -
598 812 return this.form.serializeArray().reduce( function( obj, item ) {
599 813 if ( '[]' === item.name.slice( -2 ) ) {
600 814 var name = item.name.slice( 0, -2 );
601 815
@@ -618,11 +832,10 @@
618 832 *
619 833 * @return object
620 834 */
621 835 Donation_Form.prototype.get_required_fields = function() {
622 -
623 836 var fields = this.form.find( '.charitable-fieldset .required-field' ).not( '#charitable-gateway-fields .required-field' ),
624 - method = this.get_payment_method();
837 + method = this.get_payment_method();
625 838
626 839 if ( '' !== method ) {
627 840
628 841 var gateway_fields = this.form.find( '[data-gateway=' + method + '] .required-field' );
@@ -633,22 +846,34 @@
633 846
634 847 }
635 848
636 849 return fields;
850 + };
637 851
638 - };
852 + /**
853 + * Check whether the submitted amount is greater than the maximum permitted.
854 + *
855 + * @returns boolean
856 + */
857 + Donation_Form.prototype.subtotal_exceeds_maximum = function() {
858 + return '' !== CHARITABLE_VARS.maximum_donation && this.get_subtotal() > parseFloat( CHARITABLE_VARS.maximum_donation );
859 + }
639 860
640 861 /**
641 862 * Make sure that the submitted amount is valid.
642 863 *
643 - * @return boolean
864 + * @return boolean
644 865 */
645 866 Donation_Form.prototype.is_valid_amount = function() {
646 -
647 867 var minimum = parseFloat( CHARITABLE_VARS.minimum_donation );
648 868
649 - return minimum > 0 ? this.get_amount() >= minimum : this.get_amount() >= minimum;
869 + if ( this.subtotal_exceeds_maximum() ) {
870 + return false;
871 + }
650 872
873 + return minimum > 0 || CHARITABLE_VARS.permit_0_donation
874 + ? this.get_subtotal() >= minimum
875 + : this.get_subtotal() > minimum;
651 876 };
652 877
653 878 /**
654 879 * Validate the amount. If not valid, set an error.
@@ -655,18 +880,20 @@
655 880 *
656 881 * @return boolean
657 882 */
658 883 Donation_Form.prototype.validate_amount = function() {
659 -
660 884 if ( false === this.is_valid_amount() ) {
661 - this.add_error( CHARITABLE_VARS.error_invalid_amount );
885 + if ( this.get_subtotal() > 0 && this.subtotal_exceeds_maximum() ) {
886 + this.add_error( CHARITABLE_VARS.error_max_exceeded );
887 + } else {
888 + this.add_error( CHARITABLE_VARS.error_invalid_amount );
889 + }
662 890 return false;
663 891 }
664 -
892 +
665 893 return true;
894 + };
666 895
667 - };
668 -
669 896 /**
670 897 * Verify that all required fields are filled out.
671 898 *
672 899 * @return boolean
@@ -671,27 +898,21 @@
671 898 *
672 899 * @return boolean
673 900 */
674 901 Donation_Form.prototype.validate_required_fields = function() {
675 -
676 902 var has_all_required_fields = true;
677 903
678 - var required = this.get_required_fields();
679 -
680 904 this.get_required_fields().each( function() {
681 -
682 905 if ( '' === $( this ).find( 'input, select, textarea' ).val() ) {
683 906 has_all_required_fields = false;
684 907 }
685 -
686 908 });
687 909
688 910 if ( ! has_all_required_fields ) {
689 911 this.add_error( CHARITABLE_VARS.error_required_fields );
690 - }
912 + }
691 913
692 914 return has_all_required_fields;
693 -
694 915 };
695 916
696 917 /**
697 918 * Verifies the submission and returns true if it all looks ok.
@@ -699,9 +920,8 @@
699 920 * @param this.form The submitted form.
700 921 * @return boolean
701 922 */
702 923 Donation_Form.prototype.validate = function() {
703 -
704 924 /* First clear out the errors. */
705 925 this.clear_errors();
706 926
707 927 this.validate_amount();
@@ -710,9 +930,8 @@
710 930
711 931 this.form.trigger( 'charitable:form:validate', this );
712 932
713 933 return this.errors.length === 0;
714 -
715 934 };
716 935
717 936 exports.Donation_Form = Donation_Form;
718 937
@@ -721,9 +940,8 @@
721 940 /**
722 941 * Set up Toggle object.
723 942 */
724 943 ( function( exports, $ ){
725 -
726 944 var Toggle = function() {
727 945
728 946 /**
729 947 * Hide toggle target.
@@ -739,9 +957,9 @@
739 957 var target = $el.data('charitable-toggle');
740 958
741 959 if ( target[0] !== '.' && target[0] !== '#' ) {
742 960 target = '#' + target;
743 - }
961 + }
744 962
745 963 return $( target );
746 964 }
747 965
@@ -747,9 +965,9 @@
747 965
748 966 /**
749 967 * Toggle event handler for any fields with the [data-charitable-toggle] attribute.
750 968 *
751 - * @access private
969 + * @access private
752 970 */
753 971 var on_toggle = function() {
754 972 var $this = $( this ),
755 973 $target = get_target( $this );
@@ -786,48 +1004,70 @@
786 1004 }
787 1005
788 1006 // Initialization that will be performed everytime
789 1007 return init;
790 - }
1008 + }
791 1009
792 1010 exports.Toggle = Toggle();
793 1011
794 1012 })( CHARITABLE, jQuery );
795 1013
1014 +
796 1015 /**
797 - * Set up Charitable helper functions.
1016 + * Do a version check.
1017 + *
1018 + * @since 1.6.19
1019 + *
1020 + * @param string version The version we are comparing with.
1021 + * @param string compare If compare is left empty, use the Charitable version.
1022 + * @return integer|boolean
798 1023 */
799 -( function( exports, $ ) {
1024 +CHARITABLE.VersionCompare = function( version, compare ) {
1025 + compare = compare || CHARITABLE_VARS.version;
800 1026
801 - var Helpers = function() {
1027 + if ( typeof version + typeof compare != 'stringstring')
1028 + return false;
802 1029
803 - /**
804 - * Sanitize URLs.
805 - */
806 - this.sanitize_url = function( input ) {
807 -
808 - var url = input.value.toLowerCase();
1030 + var a = version.split( '.' ),
1031 + b = compare.split( '.' ),
1032 + i = 0,
1033 + len = Math.max( a.length, b.length );
809 1034
810 - if ( !/^https?:\/\//i.test( url ) && url.length > 0 ) {
811 - url = 'http://' + url;
1035 + for ( ; i < len; i++ ) {
1036 + if ((a[i] && !b[i] && parseInt(a[i]) > 0) || (parseInt(a[i]) > parseInt(b[i]))) {
1037 + return 1;
1038 + } else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || (parseInt(a[i]) < parseInt(b[i]))) {
1039 + return -1;
1040 + }
1041 + }
812 1042
813 - input.value = url;
1043 + return 0;
1044 +};
1045 +
1046 +/**
1047 + * Set up ResetSuggestedDonations object.
1048 + */
1049 +( function( exports, $ ){
1050 +
1051 + CHARITABLE.ResetSuggestedDonations = function() {
1052 +
1053 + if ( $('.charitable-donation-options').length = 0 ) {
1054 + // the options probably aren't on this page.
1055 + return false;
1056 + }
1057 +
1058 + $( 'ul.donation-amounts input[type="radio"]' ).each( function() {
1059 + if ( $( this ).is(':checked') ) {
1060 + $( this ).closest( "li" ).addClass( "selected" );
1061 + } else {
1062 + $( this ).closest( "li" ).removeClass( "selected" );
814 1063 }
815 - }
1064 + });
816 1065
817 - };
1066 + }
818 1067
819 1068 })( CHARITABLE, jQuery );
820 1069
821 -/**
822 - * URL sanitization.
823 - *
824 - * This is provided for backwards compatibility.
825 - */
826 -CHARITABLE.SanitizeURL = function( input ) {
827 - CHARITABLE.Helpers.sanitize_url( input );
828 -};
829 -
830 1070 /***
831 1071 * Finally queue up all the scripts.
832 1072 */
833 1073 ( function( $ ) {
@@ -840,13 +1080,16 @@
840 1080 $( document ).ready( function() {
841 1081
842 1082 $( 'html' ).addClass( 'js' );
843 1083
1084 + var forms = [];
1085 +
844 1086 $( '.charitable-donation-form' ).each( function() {
845 - new CHARITABLE.Donation_Form( $( this ) );
1087 + forms.push( new CHARITABLE.Donation_Form( $( this ) ) );
846 1088 });
847 1089
848 1090 CHARITABLE.Toggle();
849 1091
1092 + CHARITABLE.ResetSuggestedDonations();
850 1093 });
851 1094
852 1095 })( jQuery );