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 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
← All changes | assets/js/charitable.js +381 -127 1.6.6 → 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,11 +808,21 @@
593 808 *
594 809 * @return object
595 810 */
596 811 Donation_Form.prototype.get_data = function() {
812 + return this.form.serializeArray().reduce( function( obj, item ) {
813 + if ( '[]' === item.name.slice( -2 ) ) {
814 + var name = item.name.slice( 0, -2 );
597 815
598 - return this.form.serializeArray().reduce( function( obj, item ) {
599 - obj[ item.name ] = item.value;
816 + if ( ! obj.hasOwnProperty( name ) ) {
817 + obj[name] = [];
818 + }
819 +
820 + obj[name].push( item.value );
821 + } else {
822 + obj[ item.name ] = item.value;
823 + }
824 +
600 825 return obj;
601 826 }, {} );
602 827
603 828 };
@@ -607,11 +832,10 @@
607 832 *
608 833 * @return object
609 834 */
610 835 Donation_Form.prototype.get_required_fields = function() {
611 -
612 836 var fields = this.form.find( '.charitable-fieldset .required-field' ).not( '#charitable-gateway-fields .required-field' ),
613 - method = this.get_payment_method();
837 + method = this.get_payment_method();
614 838
615 839 if ( '' !== method ) {
616 840
617 841 var gateway_fields = this.form.find( '[data-gateway=' + method + '] .required-field' );
@@ -622,22 +846,34 @@
622 846
623 847 }
624 848
625 849 return fields;
850 + };
626 851
627 - };
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 + }
628 860
629 861 /**
630 862 * Make sure that the submitted amount is valid.
631 863 *
632 - * @return boolean
864 + * @return boolean
633 865 */
634 866 Donation_Form.prototype.is_valid_amount = function() {
635 -
636 867 var minimum = parseFloat( CHARITABLE_VARS.minimum_donation );
637 868
638 - return minimum > 0 ? this.get_amount() >= minimum : this.get_amount() >= minimum;
869 + if ( this.subtotal_exceeds_maximum() ) {
870 + return false;
871 + }
639 872
873 + return minimum > 0 || CHARITABLE_VARS.permit_0_donation
874 + ? this.get_subtotal() >= minimum
875 + : this.get_subtotal() > minimum;
640 876 };
641 877
642 878 /**
643 879 * Validate the amount. If not valid, set an error.
@@ -644,18 +880,20 @@
644 880 *
645 881 * @return boolean
646 882 */
647 883 Donation_Form.prototype.validate_amount = function() {
648 -
649 884 if ( false === this.is_valid_amount() ) {
650 - 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 + }
651 890 return false;
652 891 }
653 -
892 +
654 893 return true;
894 + };
655 895
656 - };
657 -
658 896 /**
659 897 * Verify that all required fields are filled out.
660 898 *
661 899 * @return boolean
@@ -660,27 +898,21 @@
660 898 *
661 899 * @return boolean
662 900 */
663 901 Donation_Form.prototype.validate_required_fields = function() {
664 -
665 902 var has_all_required_fields = true;
666 903
667 - var required = this.get_required_fields();
668 -
669 904 this.get_required_fields().each( function() {
670 -
671 905 if ( '' === $( this ).find( 'input, select, textarea' ).val() ) {
672 906 has_all_required_fields = false;
673 907 }
674 -
675 908 });
676 909
677 910 if ( ! has_all_required_fields ) {
678 911 this.add_error( CHARITABLE_VARS.error_required_fields );
679 - }
912 + }
680 913
681 914 return has_all_required_fields;
682 -
683 915 };
684 916
685 917 /**
686 918 * Verifies the submission and returns true if it all looks ok.
@@ -688,9 +920,8 @@
688 920 * @param this.form The submitted form.
689 921 * @return boolean
690 922 */
691 923 Donation_Form.prototype.validate = function() {
692 -
693 924 /* First clear out the errors. */
694 925 this.clear_errors();
695 926
696 927 this.validate_amount();
@@ -699,9 +930,8 @@
699 930
700 931 this.form.trigger( 'charitable:form:validate', this );
701 932
702 933 return this.errors.length === 0;
703 -
704 934 };
705 935
706 936 exports.Donation_Form = Donation_Form;
707 937
@@ -710,9 +940,8 @@
710 940 /**
711 941 * Set up Toggle object.
712 942 */
713 943 ( function( exports, $ ){
714 -
715 944 var Toggle = function() {
716 945
717 946 /**
718 947 * Hide toggle target.
@@ -728,9 +957,9 @@
728 957 var target = $el.data('charitable-toggle');
729 958
730 959 if ( target[0] !== '.' && target[0] !== '#' ) {
731 960 target = '#' + target;
732 - }
961 + }
733 962
734 963 return $( target );
735 964 }
736 965
@@ -736,9 +965,9 @@
736 965
737 966 /**
738 967 * Toggle event handler for any fields with the [data-charitable-toggle] attribute.
739 968 *
740 - * @access private
969 + * @access private
741 970 */
742 971 var on_toggle = function() {
743 972 var $this = $( this ),
744 973 $target = get_target( $this );
@@ -775,48 +1004,70 @@
775 1004 }
776 1005
777 1006 // Initialization that will be performed everytime
778 1007 return init;
779 - }
1008 + }
780 1009
781 1010 exports.Toggle = Toggle();
782 1011
783 1012 })( CHARITABLE, jQuery );
784 1013
1014 +
785 1015 /**
786 - * 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
787 1023 */
788 -( function( exports, $ ) {
1024 +CHARITABLE.VersionCompare = function( version, compare ) {
1025 + compare = compare || CHARITABLE_VARS.version;
789 1026
790 - var Helpers = function() {
1027 + if ( typeof version + typeof compare != 'stringstring')
1028 + return false;
791 1029
792 - /**
793 - * Sanitize URLs.
794 - */
795 - this.sanitize_url = function( input ) {
796 -
797 - 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 );
798 1034
799 - if ( !/^https?:\/\//i.test( url ) && url.length > 0 ) {
800 - 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 + }
801 1042
802 - 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" );
803 1063 }
804 - }
1064 + });
805 1065
806 - };
1066 + }
807 1067
808 1068 })( CHARITABLE, jQuery );
809 1069
810 -/**
811 - * URL sanitization.
812 - *
813 - * This is provided for backwards compatibility.
814 - */
815 -CHARITABLE.SanitizeURL = function( input ) {
816 - CHARITABLE.Helpers.sanitize_url( input );
817 -};
818 -
819 1070 /***
820 1071 * Finally queue up all the scripts.
821 1072 */
822 1073 ( function( $ ) {
@@ -829,13 +1080,16 @@
829 1080 $( document ).ready( function() {
830 1081
831 1082 $( 'html' ).addClass( 'js' );
832 1083
1084 + var forms = [];
1085 +
833 1086 $( '.charitable-donation-form' ).each( function() {
834 - new CHARITABLE.Donation_Form( $( this ) );
1087 + forms.push( new CHARITABLE.Donation_Form( $( this ) ) );
835 1088 });
836 1089
837 1090 CHARITABLE.Toggle();
838 1091
1092 + CHARITABLE.ResetSuggestedDonations();
839 1093 });
840 1094
841 1095 })( jQuery );