├── LICENSE ├── assets └── js │ └── frontend │ └── add-to-cart-variation.js ├── readme.txt ├── templates └── single-product │ └── add-to-cart │ └── variable.php └── wc-variations-radio-buttons.php /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /assets/js/frontend/add-to-cart-variation.js: -------------------------------------------------------------------------------- 1 | /*global wc_add_to_cart_variation_params */ 2 | ;(function ( $, window, document, undefined ) { 3 | /** 4 | * VariationForm class which handles variation forms and attributes. 5 | */ 6 | var VariationForm = function( $form ) { 7 | var self = this; 8 | 9 | self.$form = $form; 10 | self.$attributeGroups = $form.find( '.variations .value' ); 11 | self.$attributeFields = $form.find( '.variations input[type=radio]' ); 12 | self.$singleVariation = $form.find( '.single_variation' ); 13 | self.$singleVariationWrap = $form.find( '.single_variation_wrap' ); 14 | self.$resetVariations = $form.find( '.reset_variations' ); 15 | self.$resetAlert = $form.find( '.reset_variations_alert' ) 16 | self.$product = $form.closest( '.product' ); 17 | self.variationData = $form.data( 'product_variations' ); 18 | self.useAjax = false === self.variationData; 19 | self.xhr = false; 20 | self.loading = true; 21 | 22 | // Initial state. 23 | self.$singleVariationWrap.show(); 24 | self.$form.off( '.wc-variation-form' ); 25 | 26 | // Methods. 27 | self.getChosenAttributes = self.getChosenAttributes.bind( self ); 28 | self.findMatchingVariations = self.findMatchingVariations.bind( self ); 29 | self.isMatch = self.isMatch.bind( self ); 30 | self.toggleResetLink = self.toggleResetLink.bind( self ); 31 | self.showNoMatchingVariationsMsg = self.showNoMatchingVariationsMsg.bind( self ); 32 | 33 | // Events. 34 | $form.on( 'click.wc-variation-form', '.reset_variations', { variationForm: self }, self.onReset ); 35 | $form.on( 'reload_product_variations', { variationForm: self }, self.onReload ); 36 | $form.on( 'hide_variation', { variationForm: self }, self.onHide ); 37 | $form.on( 'show_variation', { variationForm: self }, self.onShow ); 38 | $form.on( 'click', '.single_add_to_cart_button', { variationForm: self }, self.onAddToCart ); 39 | $form.on( 'reset_data', { variationForm: self }, self.onResetDisplayedVariation ); 40 | $form.on( 'reset_focus', { variationForm: self }, self.onResetVariationFocus ); 41 | $form.on( 'announce_reset', { variationForm: self }, self.onAnnounceReset ); 42 | $form.on( 'clear_reset_announcement', { variationForm: self }, self.onClearResetAnnouncement ); 43 | $form.on( 'reset_image', { variationForm: self }, self.onResetImage ); 44 | $form.on( 'change.wc-variation-form', '.variations input[type=radio]', { variationForm: self }, self.onChange ); 45 | $form.on( 'found_variation.wc-variation-form', { variationForm: self }, self.onFoundVariation ); 46 | $form.on( 'check_variations.wc-variation-form', { variationForm: self }, self.onFindVariation ); 47 | $form.on( 'update_variation_values.wc-variation-form', { variationForm: self }, self.onUpdateAttributes ); 48 | 49 | // Check variations once init. 50 | // Init after gallery. 51 | setTimeout( function() { 52 | $form.trigger( 'check_variations' ); 53 | $form.trigger('wc_variation_form', self); 54 | self.loading = false; 55 | }, 100 ); 56 | }; 57 | 58 | /** 59 | * Reset all fields. 60 | */ 61 | VariationForm.prototype.onReset = function( event ) { 62 | event.preventDefault(); 63 | event.data.variationForm.$attributeFields.prop('checked', false).change(); 64 | event.data.variationForm.$form.trigger('announce_reset'); 65 | event.data.variationForm.$form.trigger('reset_data'); 66 | event.data.variationForm.$form.trigger('reset_focus'); 67 | }; 68 | 69 | /** 70 | * Reload variation data from the DOM. 71 | */ 72 | VariationForm.prototype.onReload = function( event ) { 73 | var form = event.data.variationForm; 74 | form.variationData = form.$form.data( 'product_variations' ); 75 | form.useAjax = false === form.variationData; 76 | form.$form.trigger( 'check_variations' ); 77 | }; 78 | 79 | /** 80 | * When a variation is hidden. 81 | */ 82 | VariationForm.prototype.onHide = function( event ) { 83 | event.preventDefault(); 84 | event.data.variationForm.$form.find( '.single_add_to_cart_button' ).removeClass( 'wc-variation-is-unavailable' ).addClass( 'disabled wc-variation-selection-needed' ); 85 | event.data.variationForm.$form.find( '.woocommerce-variation-add-to-cart' ).removeClass( 'woocommerce-variation-add-to-cart-enabled' ).addClass( 'woocommerce-variation-add-to-cart-disabled' ); 86 | }; 87 | 88 | /** 89 | * When a variation is shown. 90 | */ 91 | VariationForm.prototype.onShow = function( event, variation, purchasable ) { 92 | event.preventDefault(); 93 | if ( purchasable ) { 94 | event.data.variationForm.$form.find( '.single_add_to_cart_button' ).removeClass( 'disabled wc-variation-selection-needed wc-variation-is-unavailable' ); 95 | event.data.variationForm.$form.find( '.woocommerce-variation-add-to-cart' ).removeClass( 'woocommerce-variation-add-to-cart-disabled' ).addClass( 'woocommerce-variation-add-to-cart-enabled' ); 96 | } else { 97 | event.data.variationForm.$form.find( '.single_add_to_cart_button' ).removeClass( 'wc-variation-selection-needed' ).addClass( 'disabled wc-variation-is-unavailable' ); 98 | event.data.variationForm.$form.find( '.woocommerce-variation-add-to-cart' ).removeClass( 'woocommerce-variation-add-to-cart-enabled' ).addClass( 'woocommerce-variation-add-to-cart-disabled' ); 99 | } 100 | 101 | // If present, the media element library needs initialized on the variation description. 102 | if (wp.mediaelement) { 103 | event.data.variationForm.$form.find('.wp-audio-shortcode, .wp-video-shortcode') 104 | .not('.mejs-container') 105 | .filter( 106 | function () { 107 | return !$(this).parent().hasClass('mejs-mediaelement'); 108 | } 109 | ) 110 | .mediaelementplayer(wp.mediaelement.settings); 111 | } 112 | }; 113 | 114 | /** 115 | * When the cart button is pressed. 116 | */ 117 | VariationForm.prototype.onAddToCart = function( event ) { 118 | if ( $( this ).is('.disabled') ) { 119 | event.preventDefault(); 120 | 121 | if ( $( this ).is('.wc-variation-is-unavailable') ) { 122 | window.alert( wc_add_to_cart_variation_params.i18n_unavailable_text ); 123 | } else if ( $( this ).is('.wc-variation-selection-needed') ) { 124 | window.alert( wc_add_to_cart_variation_params.i18n_make_a_selection_text ); 125 | } 126 | } 127 | }; 128 | 129 | /** 130 | * When displayed variation data is reset. 131 | */ 132 | VariationForm.prototype.onResetDisplayedVariation = function( event ) { 133 | var form = event.data.variationForm; 134 | form.$product.find( '.product_meta' ).find( '.sku' ).wc_reset_content(); 135 | form.$product.find('.product_weight, .woocommerce-product-attributes-item--weight .woocommerce-product-attributes-item__value').wc_reset_content(); 136 | form.$product.find('.product_dimensions, .woocommerce-product-attributes-item--dimensions .woocommerce-product-attributes-item__value').wc_reset_content(); 137 | form.$form.trigger( 'reset_image' ); 138 | form.$singleVariation.slideUp( 200 ).trigger( 'hide_variation' ); 139 | }; 140 | 141 | /** 142 | * Announce reset to screen readers. 143 | */ 144 | VariationForm.prototype.onAnnounceReset = function (event) { 145 | event.data.variationForm.$resetAlert.text(wc_add_to_cart_variation_params.i18n_reset_alert_text); 146 | } 147 | 148 | /** 149 | * Focus variation reset 150 | */ 151 | VariationForm.prototype.onResetVariationFocus = function (event) { 152 | event.data.variationForm.$attributeFields[0].focus(); 153 | } 154 | 155 | /** Clear reset announcement */ 156 | VariationForm.prototype.onClearResetAnnouncement = function (event) { 157 | event.data.variationForm.$resetAlert.text(''); 158 | } 159 | 160 | /** 161 | * When the product image is reset. 162 | */ 163 | VariationForm.prototype.onResetImage = function( event ) { 164 | event.data.variationForm.$form.wc_variations_image_update( false ); 165 | }; 166 | 167 | /** 168 | * Looks for matching variations for current checked attributes. 169 | */ 170 | VariationForm.prototype.onFindVariation = function (event, chosenAttributes ) { 171 | var form = event.data.variationForm, 172 | attributes = 'undefined' !== typeof chosenAttributes ? chosenAttributes : form.getChosenAttributes(), 173 | currentAttributes = attributes.data; 174 | 175 | if ( attributes.count && attributes.count === attributes.chosenCount ) { 176 | if ( form.useAjax ) { 177 | if ( form.xhr ) { 178 | form.xhr.abort(); 179 | } 180 | form.$form.block( { message: null, overlayCSS: { background: '#fff', opacity: 0.6 } } ); 181 | currentAttributes.product_id = parseInt( form.$form.data( 'product_id' ), 10 ); 182 | currentAttributes.custom_data = form.$form.data( 'custom_data' ); 183 | form.xhr = $.ajax( { 184 | url: wc_add_to_cart_variation_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_variation' ), 185 | type: 'POST', 186 | data: currentAttributes, 187 | success: function( variation ) { 188 | if ( variation ) { 189 | form.$form.trigger( 'found_variation', [ variation ] ); 190 | } else { 191 | form.$form.trigger('reset_data'); 192 | attributes.chosenCount = 0; 193 | 194 | if (!form.loading) { 195 | form.showNoMatchingVariationsMsg(); 196 | } 197 | } 198 | }, 199 | complete: function() { 200 | form.$form.unblock(); 201 | } 202 | } ); 203 | } else { 204 | form.$form.trigger( 'update_variation_values' ); 205 | 206 | var matching_variations = form.findMatchingVariations( form.variationData, currentAttributes ), 207 | variation = matching_variations.shift(); 208 | 209 | if ( variation ) { 210 | form.$form.trigger( 'found_variation', [ variation ] ); 211 | } else { 212 | form.$form.trigger('reset_data'); 213 | attributes.chosenCount = 0; 214 | 215 | if (!form.loading) { 216 | form.showNoMatchingVariationsMsg(); 217 | } 218 | } 219 | } 220 | } else { 221 | form.$form.trigger( 'update_variation_values' ); 222 | form.$form.trigger( 'reset_data' ); 223 | } 224 | 225 | // Show reset link. 226 | form.toggleResetLink( attributes.chosenCount > 0 ); 227 | }; 228 | 229 | /** 230 | * Triggered when a variation has been found which matches all attributes. 231 | */ 232 | VariationForm.prototype.onFoundVariation = function( event, variation ) { 233 | var form = event.data.variationForm, 234 | $sku = form.$product.find( '.product_meta' ).find( '.sku' ), 235 | $weight = form.$product.find( 236 | '.product_weight, .woocommerce-product-attributes-item--weight .woocommerce-product-attributes-item__value' 237 | ), 238 | $dimensions = form.$product.find( 239 | '.product_dimensions, .woocommerce-product-attributes-item--dimensions .woocommerce-product-attributes-item__value' 240 | ), 241 | $qty_input = form.$singleVariationWrap.find( '.quantity input.qty[name="quantity"]' ), 242 | $qty = form.$singleVariationWrap.find( '.quantity' ), 243 | purchasable = true, 244 | variation_id = '', 245 | template = false, 246 | $template_html = ''; 247 | 248 | if ( variation.sku ) { 249 | $sku.wc_set_content( variation.sku ); 250 | } else { 251 | $sku.wc_reset_content(); 252 | } 253 | 254 | if ( variation.weight ) { 255 | $weight.wc_set_content( variation.weight_html ); 256 | } else { 257 | $weight.wc_reset_content(); 258 | } 259 | 260 | if ( variation.dimensions ) { 261 | $dimensions.wc_set_content( variation.dimensions_html ); 262 | } else { 263 | $dimensions.wc_reset_content(); 264 | } 265 | 266 | form.$form.wc_variations_image_update( variation ); 267 | 268 | if ( ! variation.variation_is_visible ) { 269 | template = wp_template( 'unavailable-variation-template' ); 270 | } else { 271 | template = wp_template( 'variation-template' ); 272 | variation_id = variation.variation_id; 273 | } 274 | 275 | $template_html = template( { 276 | variation: variation 277 | } ); 278 | $template_html = $template_html.replace( '/**/', '' ); 280 | 281 | form.$singleVariation.html( $template_html ); 282 | form.$form.find( 'input[name="variation_id"], input.variation_id' ).val( variation.variation_id ).change(); 283 | 284 | // Hide or show qty input 285 | if ( variation.is_sold_individually === 'yes' ) { 286 | $qty_input.val( '1' ).attr( 'min', '1' ).attr( 'max', '' ).trigger( 'change' ); 287 | $qty.hide(); 288 | } else { 289 | var qty_val = parseFloat($qty_input.val()); 290 | 291 | if (isNaN(qty_val)) { 292 | qty_val = variation.min_qty; 293 | } else { 294 | qty_val = qty_val > parseFloat(variation.max_qty) ? variation.max_qty : qty_val; 295 | qty_val = qty_val < parseFloat(variation.min_qty) ? variation.min_qty : qty_val; 296 | } 297 | 298 | $qty_input.attr('min', variation.min_qty).attr('max', variation.max_qty).val(qty_val).trigger('change'); 299 | $qty.show(); 300 | } 301 | 302 | // Enable or disable the add to cart button 303 | if ( ! variation.is_purchasable || ! variation.is_in_stock || ! variation.variation_is_visible ) { 304 | purchasable = false; 305 | } 306 | 307 | // Reveal 308 | if ( $.trim( form.$singleVariation.text() ) ) { 309 | form.$singleVariation.slideDown( 200 ).trigger( 'show_variation', [ variation, purchasable ] ); 310 | } else { 311 | form.$singleVariation.show().trigger( 'show_variation', [ variation, purchasable ] ); 312 | } 313 | }; 314 | 315 | /** 316 | * Triggered when an attribute field changes. 317 | */ 318 | VariationForm.prototype.onChange = function( event ) { 319 | var form = event.data.variationForm; 320 | 321 | form.$form.find( 'input[name="variation_id"], input.variation_id' ).val( '' ).change(); 322 | form.$form.trigger( 'clear_reset_announcement' ); 323 | form.$form.find( '.wc-no-matching-variations' ).remove(); 324 | 325 | if ( form.useAjax ) { 326 | form.$form.trigger( 'check_variations' ); 327 | } else { 328 | form.$form.trigger( 'woocommerce_variation_select_change' ); 329 | form.$form.trigger( 'check_variations' ); 330 | $( this ).blur(); 331 | } 332 | 333 | // Custom event for when variation selection has been changed 334 | form.$form.trigger( 'woocommerce_variation_has_changed' ); 335 | }; 336 | 337 | /** 338 | * Escape quotes in a string. 339 | * @param {string} string 340 | * @return {string} 341 | */ 342 | VariationForm.prototype.addSlashes = function( string ) { 343 | string = string.replace( /'/g, '\\\'' ); 344 | string = string.replace( /"/g, '\\\"' ); 345 | return string; 346 | }; 347 | 348 | /** 349 | * Updates attributes in the DOM to show valid values. 350 | */ 351 | VariationForm.prototype.onUpdateAttributes = function( event ) { 352 | var form = event.data.variationForm, 353 | attributes = form.getChosenAttributes(), 354 | currentAttributes = attributes.data; 355 | 356 | if ( form.useAjax ) { 357 | return; 358 | } 359 | 360 | // Loop through radio buttons and disable/enable based on selections. 361 | form.$attributeGroups.each( function( index, el ) { 362 | var current_attr = $( el ), 363 | $fields = current_attr.find( 'input[type=radio]' ), 364 | current_attr_name = $fields.data( 'attribute_name' ) || $fields.attr( 'name' ); 365 | 366 | // The attribute of this radio button should not be taken into account when calculating its matching variations: 367 | // The constraints of this attribute are shaped by the values of the other attributes. 368 | var checkAttributes = $.extend( true, {}, currentAttributes ); 369 | 370 | checkAttributes[ current_attr_name ] = ''; 371 | 372 | var variations = form.findMatchingVariations( form.variationData, checkAttributes ); 373 | 374 | $fields.prop( 'disabled', 'disabled' ); 375 | 376 | // Loop through variations. 377 | for ( var num in variations ) { 378 | if ( typeof( variations[ num ] ) !== 'undefined' && variations[ num ].variation_is_active ) { 379 | var variationAttributes = variations[ num ].attributes; 380 | 381 | for ( var attr_name in variationAttributes ) { 382 | if ( variationAttributes.hasOwnProperty( attr_name ) && attr_name === current_attr_name ) { 383 | var attr_val = variationAttributes[ attr_name ]; 384 | 385 | if ( attr_val ) { 386 | // Remove disabled. 387 | $fields.filter( '[value="' + form.addSlashes( attr_val ) + '"]' ).prop( 'disabled', false ); 388 | } else { 389 | // Enable all radio buttons of attribute. 390 | $fields.prop( 'disabled', false ); 391 | } 392 | } 393 | } 394 | } 395 | } 396 | }); 397 | 398 | // Custom event for when variations have been updated. 399 | form.$form.trigger( 'woocommerce_update_variation_values' ); 400 | }; 401 | 402 | /** 403 | * Get chosen attributes from form. 404 | * @return array 405 | */ 406 | VariationForm.prototype.getChosenAttributes = function() { 407 | var data = {}; 408 | var count = 0; 409 | var chosen = 0; 410 | 411 | this.$attributeGroups.each( function() { 412 | var $fields = $( this ).find( 'input[type=radio]' ); 413 | 414 | var attribute_name = $fields.data( 'attribute_name' ) || $fields.attr( 'name' ); 415 | var value = $fields.filter(':checked').val() || ''; 416 | 417 | if ( value.length > 0 ) { 418 | chosen ++; 419 | } 420 | 421 | count ++; 422 | data[ attribute_name ] = value; 423 | }); 424 | 425 | return { 426 | 'count' : count, 427 | 'chosenCount': chosen, 428 | 'data' : data 429 | }; 430 | }; 431 | 432 | /** 433 | * Find matching variations for attributes. 434 | */ 435 | VariationForm.prototype.findMatchingVariations = function( variations, attributes ) { 436 | var matching = []; 437 | for ( var i = 0; i < variations.length; i++ ) { 438 | var variation = variations[i]; 439 | 440 | if ( this.isMatch( variation.attributes, attributes ) ) { 441 | matching.push( variation ); 442 | } 443 | } 444 | return matching; 445 | }; 446 | 447 | /** 448 | * See if attributes match. 449 | * @return {Boolean} 450 | */ 451 | VariationForm.prototype.isMatch = function( variation_attributes, attributes ) { 452 | var match = true; 453 | for ( var attr_name in variation_attributes ) { 454 | if ( variation_attributes.hasOwnProperty( attr_name ) ) { 455 | var val1 = variation_attributes[ attr_name ]; 456 | var val2 = attributes[ attr_name ]; 457 | if ( val1 !== undefined && val2 !== undefined && val1.length !== 0 && val2.length !== 0 && val1 !== val2 ) { 458 | match = false; 459 | } 460 | } 461 | } 462 | return match; 463 | }; 464 | 465 | /** 466 | * Show or hide the reset link. 467 | */ 468 | VariationForm.prototype.toggleResetLink = function (on) { 469 | this.$resetAlert.text(''); 470 | if ( on ) { 471 | if ( this.$resetVariations.css( 'visibility' ) === 'hidden' ) { 472 | this.$resetVariations.css('visibility', 'visible').hide().fadeIn(); 473 | this.$resetVariations.css('display', 'inline-block'); 474 | } 475 | } else { 476 | this.$resetVariations.css('display', 'none'); 477 | this.$resetVariations.css('visibility', 'hidden'); 478 | } 479 | }; 480 | 481 | /** 482 | * Show no matching variation message. 483 | */ 484 | VariationForm.prototype.showNoMatchingVariationsMsg = function () { 485 | this.$form 486 | .find('.single_variation') 487 | .after( 488 | '
' + 489 | '

' + 490 | wc_add_to_cart_variation_params.i18n_no_matching_variations_text + 491 | '

' + 492 | '
' 493 | ) 494 | .next('div') 495 | .find('.wc-no-matching-variations') 496 | .slideDown(200); 497 | }; 498 | 499 | /** 500 | * Function to call wc_variation_form on jquery selector. 501 | */ 502 | $.fn.wc_variation_form = function() { 503 | new VariationForm( this ); 504 | return this; 505 | }; 506 | 507 | /** 508 | * Stores the default text for an element so it can be reset later 509 | */ 510 | $.fn.wc_set_content = function( content ) { 511 | if ( undefined === this.attr( 'data-o_content' ) ) { 512 | this.attr( 'data-o_content', this.text() ); 513 | } 514 | this.text( content ); 515 | }; 516 | 517 | /** 518 | * Stores the default text for an element so it can be reset later 519 | */ 520 | $.fn.wc_reset_content = function() { 521 | if ( undefined !== this.attr( 'data-o_content' ) ) { 522 | this.text( this.attr( 'data-o_content' ) ); 523 | } 524 | }; 525 | 526 | /** 527 | * Stores a default attribute for an element so it can be reset later 528 | */ 529 | $.fn.wc_set_variation_attr = function( attr, value ) { 530 | if ( undefined === this.attr( 'data-o_' + attr ) ) { 531 | this.attr( 'data-o_' + attr, ( ! this.attr( attr ) ) ? '' : this.attr( attr ) ); 532 | } 533 | if ( false === value ) { 534 | this.removeAttr( attr ); 535 | } else { 536 | this.attr( attr, value ); 537 | } 538 | }; 539 | 540 | /** 541 | * Reset a default attribute for an element so it can be reset later 542 | */ 543 | $.fn.wc_reset_variation_attr = function( attr ) { 544 | if ( undefined !== this.attr( 'data-o_' + attr ) ) { 545 | this.attr( attr, this.attr( 'data-o_' + attr ) ); 546 | } 547 | }; 548 | 549 | /** 550 | * Reset the slide position if the variation has a different image than the current one 551 | */ 552 | $.fn.wc_maybe_trigger_slide_position_reset = function( variation ) { 553 | var $form = $( this ), 554 | $product = $form.closest( '.product' ), 555 | $product_gallery = $product.find( '.images' ), 556 | reset_slide_position = false, 557 | new_image_id = ( variation && variation.image_id ) ? variation.image_id : ''; 558 | 559 | if ( $form.attr( 'current-image' ) !== new_image_id ) { 560 | reset_slide_position = true; 561 | } 562 | 563 | $form.attr( 'current-image', new_image_id ); 564 | 565 | if ( reset_slide_position ) { 566 | $product_gallery.trigger( 'woocommerce_gallery_reset_slide_position' ); 567 | } 568 | }; 569 | 570 | /** 571 | * Sets product images for the chosen variation 572 | */ 573 | $.fn.wc_variations_image_update = function( variation ) { 574 | var $form = this, 575 | $product = $form.closest( '.product' ), 576 | $product_gallery = $product.find( '.images' ), 577 | $gallery_nav = $product.find( '.flex-control-nav' ), 578 | $gallery_img = $gallery_nav.find( 'li:eq(0) img' ), 579 | $product_img_wrap = $product_gallery 580 | .find( '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' ) 581 | .eq( 0 ), 582 | $product_img = $product_img_wrap.find( '.wp-post-image' ), 583 | $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 584 | 585 | if ( variation && variation.image && variation.image.src && variation.image.src.length > 1 ) { 586 | // See if the gallery has an image with the same original src as the image we want to switch to. 587 | var galleryHasImage = $gallery_nav.find( 'li img[data-o_src="' + variation.image.gallery_thumbnail_src + '"]' ).length > 0; 588 | 589 | // If the gallery has the image, reset the images. We'll scroll to the correct one. 590 | if ( galleryHasImage ) { 591 | $form.wc_variations_image_reset(); 592 | } 593 | 594 | // See if gallery has a matching image we can slide to. 595 | var slideToImage = $gallery_nav.find( 'li img[src="' + variation.image.gallery_thumbnail_src + '"]' ); 596 | 597 | if ( slideToImage.length > 0 ) { 598 | slideToImage.trigger( 'click' ); 599 | $form.attr( 'current-image', variation.image_id ); 600 | window.setTimeout( function() { 601 | $( window ).trigger( 'resize' ); 602 | $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 603 | }, 20 ); 604 | return; 605 | } 606 | 607 | $product_img.wc_set_variation_attr( 'src', variation.image.src ); 608 | $product_img.wc_set_variation_attr( 'height', variation.image.src_h ); 609 | $product_img.wc_set_variation_attr( 'width', variation.image.src_w ); 610 | $product_img.wc_set_variation_attr( 'srcset', variation.image.srcset ); 611 | $product_img.wc_set_variation_attr( 'sizes', variation.image.sizes ); 612 | $product_img.wc_set_variation_attr( 'title', variation.image.title ); 613 | $product_img.wc_set_variation_attr( 'data-caption', variation.image.caption ); 614 | $product_img.wc_set_variation_attr( 'alt', variation.image.alt ); 615 | $product_img.wc_set_variation_attr( 'data-src', variation.image.full_src ); 616 | $product_img.wc_set_variation_attr( 'data-large_image', variation.image.full_src ); 617 | $product_img.wc_set_variation_attr( 'data-large_image_width', variation.image.full_src_w ); 618 | $product_img.wc_set_variation_attr( 'data-large_image_height', variation.image.full_src_h ); 619 | $product_img_wrap.wc_set_variation_attr( 'data-thumb', variation.image.src ); 620 | $gallery_img.wc_set_variation_attr( 'src', variation.image.gallery_thumbnail_src ); 621 | $product_link.wc_set_variation_attr( 'href', variation.image.full_src ); 622 | } else { 623 | $form.wc_variations_image_reset(); 624 | } 625 | 626 | window.setTimeout( function() { 627 | $( window ).trigger( 'resize' ); 628 | $form.wc_maybe_trigger_slide_position_reset( variation ); 629 | $product_gallery.trigger( 'woocommerce_gallery_init_zoom' ); 630 | }, 20 ); 631 | }; 632 | 633 | /** 634 | * Reset main image to defaults. 635 | */ 636 | $.fn.wc_variations_image_reset = function() { 637 | var $form = this, 638 | $product = $form.closest( '.product' ), 639 | $product_gallery = $product.find( '.images' ), 640 | $gallery_nav = $product.find( '.flex-control-nav' ), 641 | $gallery_img = $gallery_nav.find( 'li:eq(0) img' ), 642 | $product_img_wrap = $product_gallery 643 | .find( '.woocommerce-product-gallery__image, .woocommerce-product-gallery__image--placeholder' ) 644 | .eq( 0 ), 645 | $product_img = $product_img_wrap.find( '.wp-post-image' ), 646 | $product_link = $product_img_wrap.find( 'a' ).eq( 0 ); 647 | 648 | $product_img.wc_reset_variation_attr( 'src' ); 649 | $product_img.wc_reset_variation_attr( 'width' ); 650 | $product_img.wc_reset_variation_attr( 'height' ); 651 | $product_img.wc_reset_variation_attr( 'srcset' ); 652 | $product_img.wc_reset_variation_attr( 'sizes' ); 653 | $product_img.wc_reset_variation_attr( 'title' ); 654 | $product_img.wc_reset_variation_attr( 'data-caption' ); 655 | $product_img.wc_reset_variation_attr( 'alt' ); 656 | $product_img.wc_reset_variation_attr( 'data-src' ); 657 | $product_img.wc_reset_variation_attr( 'data-large_image' ); 658 | $product_img.wc_reset_variation_attr( 'data-large_image_width' ); 659 | $product_img.wc_reset_variation_attr( 'data-large_image_height' ); 660 | $product_img_wrap.wc_reset_variation_attr( 'data-thumb' ); 661 | $gallery_img.wc_reset_variation_attr( 'src' ); 662 | $product_link.wc_reset_variation_attr( 'href' ); 663 | }; 664 | 665 | $(function() { 666 | if ( typeof wc_add_to_cart_variation_params !== 'undefined' ) { 667 | $( '.variations_form' ).each( function() { 668 | $( this ).wc_variation_form(); 669 | }); 670 | } 671 | }); 672 | 673 | /** 674 | * Matches inline variation objects to chosen attributes 675 | * @deprecated 2.6.9 676 | * @type {Object} 677 | */ 678 | var wc_variation_form_matcher = { 679 | find_matching_variations: function( product_variations, settings ) { 680 | var matching = []; 681 | for ( var i = 0; i < product_variations.length; i++ ) { 682 | var variation = product_variations[i]; 683 | 684 | if ( wc_variation_form_matcher.variations_match( variation.attributes, settings ) ) { 685 | matching.push( variation ); 686 | } 687 | } 688 | return matching; 689 | }, 690 | variations_match: function( attrs1, attrs2 ) { 691 | var match = true; 692 | for ( var attr_name in attrs1 ) { 693 | if ( attrs1.hasOwnProperty( attr_name ) ) { 694 | var val1 = attrs1[ attr_name ]; 695 | var val2 = attrs2[ attr_name ]; 696 | if ( val1 !== undefined && val2 !== undefined && val1.length !== 0 && val2.length !== 0 && val1 !== val2 ) { 697 | match = false; 698 | } 699 | } 700 | } 701 | return match; 702 | } 703 | }; 704 | 705 | /** 706 | * Avoids using wp.template where possible in order to be CSP compliant. 707 | * wp.template uses internally eval(). 708 | * @param {string} templateId 709 | * @return {Function} 710 | */ 711 | var wp_template = function (templateId) { 712 | var html = document.getElementById('tmpl-' + templateId).textContent; 713 | var hard = false; 714 | // any <# #> interpolate (evaluate). 715 | hard = hard || /<#\s?data\./.test(html); 716 | // any data that is NOT data.variation. 717 | hard = hard || /{{{?\s?data\.(?!variation\.).+}}}?/.test(html); 718 | // any data access deeper than 1 level e.g. 719 | // data.variation.object.item 720 | // data.variation.object['item'] 721 | // data.variation.array[0] 722 | hard = hard || /{{{?\s?data\.variation\.[\w-]*[^\s}]/.test(html); 723 | if (hard) { 724 | return wp.template(templateId); 725 | } 726 | return function template(data) { 727 | var variation = data.variation || {}; 728 | return html.replace(/({{{?)\s?data\.variation\.([\w-]*)\s?(}}}?)/g, function (_, open, key, close) { 729 | // Error in the format, ignore. 730 | if (open.length !== close.length) { 731 | return ''; 732 | } 733 | var replacement = variation[key] || ''; 734 | // {{{ }}} => interpolate (unescaped). 735 | // {{ }} => interpolate (escaped). 736 | // https://codex.wordpress.org/Javascript_Reference/wp.template 737 | if (open.length === 2) { 738 | return window.escape(replacement); 739 | } 740 | return replacement; 741 | }); 742 | }; 743 | }; 744 | 745 | })( jQuery, window, document ); 746 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WC Variations Radio Buttons === 2 | Contributors: mantish, mariankadanka 3 | Donate link: mailto:paypal@8manos.com 4 | Tags: woocommerce, variations, radio buttons, variations radio, variations radio buttons 5 | Requires at least: 4.4 6 | Tested up to: 6.7 7 | Stable tag: 2.1.1 8 | License: GPLv2 or later 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | Variations Radio Buttons for WooCommerce. Let your customers choose product variations using radio buttons instead of dropdowns. 12 | 13 | == Description == 14 | 15 | Radio buttons are more friendly than dropdown selects. This plugin will help you have a better UX in your WooCommerce site, as your customers will see all your variations without having to click on a dropdown. 16 | 17 | The radio buttons will be toggled dinamically so your customers won't be able to choose non existent variations. 18 | 19 | ### Theme Compatibility 20 | 21 | This plugin may not work out of the box with some themes. This is due to a theme having its own template for variation selection (single-product/add-to-cart/variable.php). 22 | 23 | To make this plugin work with a non compatible theme, a [child theme](http://themify.me/docs/child-themes) has to be created. 24 | Then copy plugins/wc-variations-radio-buttons/templates/single-product/add-to-cart/variable.php to themes/your-child-theme/woocommerce/single-product/add-to-cart/variable.php 25 | 26 | ### Github 27 | 28 | Source code and contributions at [github](https://github.com/8manos/wc-variations-radio-buttons) 29 | 30 | == Changelog == 31 | 32 | = 2.1.1 = 33 | * Fix variation not found message display 34 | * Update template and script to match the latest in WooCommerce 35 | 36 | = 2.1.0 = 37 | * Add compatibility with WooCommerce HPOS feature. 38 | * Update template and script to match the latest in WooCommerce 39 | * Fix error selecting attributes of products with many variations 40 | 41 | = 2.0.5 = 42 | * Improve compatibility with WordPress 5.5+ by removing depreacted jQuery methods. 43 | 44 | = 2.0.2 = 45 | * Template file updated to match changes in WooCommerce 3.4. 46 | * Fix duplication of custom fields. 47 | 48 | = 2.0.1 = 49 | * Fix issues in archive pages, where multiple products are present. 50 | * Fix several issues with the gallery. 51 | * Fix product dimensions and weight. 52 | * Radio buttons disabled for product bundles. 53 | * Add class so each attribute can be styled. 54 | * Add the attribute name to the `woocommerce_variation_option_name` filter. 55 | 56 | = 2.0.0 = 57 | * Updated to match changes in WooCommerce 3.0. 58 | * This version is not compatible with WooCommerce 2.x. 59 | * Fix issues with the variation image not changing. 60 | 61 | = 1.1.5 = 62 | * Fix error when using product_page WooCommerce shortcode. 63 | * Show alert when clicking disabled add to cart button. 64 | 65 | = 1.1.4 = 66 | * Avoid overwriting of custom add-to-cart button. 67 | 68 | = 1.1.3 = 69 | * Fixes clear button that got screwed on last version. 70 | 71 | = 1.1.2 = 72 | * Updated to match changes in WooCommerce 2.5. 73 | * Image disappearing in certain themes fixed. 74 | 75 | = 1.1.1 = 76 | * Support for responsive images as in WordPress 4.4 and WooCommerce 2.4.11. 77 | * Checked compatibility with WooCommerce 2.5.0-RC1. 78 | 79 | = 1.1.0 = 80 | * Full compatibility with WooCommerce 2.4. 81 | * Variations are called via AJAX when there are many variations. 82 | * Changed version requirements: At least WordPress 4.1 and WooCommerce 2.4. 83 | 84 | = 1.0.3 = 85 | * Now works with woocommerce_variation_is_active filter. 86 | * Fix bug that prevented the plugin to work with custom attributes. 87 | * Hopefuly no more "The plugin does not have a valid header." message. 88 | 89 | = 1.0.2 = 90 | * Now works better with some themes. 91 | 92 | = 1.0.1 = 93 | * WooCommerce 2.4 compatibility. 94 | 95 | = 1.0 = 96 | * First release. 97 | -------------------------------------------------------------------------------- /templates/single-product/add-to-cart/variable.php: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 | 28 | 29 | 30 |

31 | 32 | 33 | 34 | $options) : ?> 35 | 36 | 37 | 38 | 47 | 68 | 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | version, '3.4.0') < 0) { 84 | do_action('woocommerce_before_add_to_cart_button'); 85 | } 86 | ?> 87 | 88 |
89 | 94 |
95 | 96 | version, '3.4.0') < 0) { 98 | do_action('woocommerce_after_add_to_cart_button'); 99 | } 100 | ?> 101 | 102 | 103 | 104 |
105 | 106 | 108 | -------------------------------------------------------------------------------- /wc-variations-radio-buttons.php: -------------------------------------------------------------------------------- 1 | plugin_path ) { 43 | return $this->plugin_path; 44 | } 45 | 46 | return $this->plugin_path = plugin_dir_path( __FILE__ ); 47 | } 48 | 49 | public function get_plugin_url() { 50 | 51 | if ( $this->plugin_url ) { 52 | return $this->plugin_url; 53 | } 54 | 55 | return $this->plugin_url = plugin_dir_url( __FILE__ ); 56 | } 57 | 58 | public function locate_template( $template, $template_name, $template_path ) { 59 | global $woocommerce; 60 | 61 | $_template = $template; 62 | 63 | if ( ! $template_path ) { 64 | $template_path = $woocommerce->template_url; 65 | } 66 | 67 | $plugin_path = $this->get_plugin_path() . 'templates/'; 68 | 69 | // Look within passed path within the theme - this is priority 70 | $template = locate_template( array( 71 | $template_path . $template_name, 72 | $template_name 73 | ) ); 74 | 75 | // Modification: Get the template from this plugin, if it exists 76 | if ( ! $template && file_exists( $plugin_path . $template_name ) ) { 77 | $template = $plugin_path . $template_name; 78 | } 79 | 80 | // Use default template 81 | if ( ! $template ) { 82 | $template = $_template; 83 | } 84 | 85 | return $template; 86 | } 87 | 88 | function load_scripts() { 89 | // Don't load JS if current product type is bundle to prevent the page from not working 90 | if (!(wc_get_product() && wc_get_product()->is_type('bundle'))) { 91 | wp_deregister_script( 'wc-add-to-cart-variation' ); 92 | wp_register_script( 'wc-add-to-cart-variation', $this->get_plugin_url() . 'assets/js/frontend/add-to-cart-variation.js', array( 'jquery', 'wp-util' ), self::VERSION ); 93 | } 94 | } 95 | 96 | /** 97 | * Mark the plugin as compatible with the HPOS feature 98 | * 99 | * @return void 100 | */ 101 | function hposCompatibility(): void { 102 | if(class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) 103 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true); 104 | } 105 | } 106 | 107 | new WC_Radio_Buttons(); 108 | 109 | if ( ! function_exists( 'print_attribute_radio' ) ) { 110 | function print_attribute_radio( $checked_value, $value, $label, $name ) { 111 | global $product; 112 | 113 | $input_name = 'attribute_' . esc_attr( $name ) ; 114 | $esc_value = esc_attr( $value ); 115 | $id = esc_attr( $name . '_v_' . $value . $product->get_id() ); //added product ID at the end of the name to target single products 116 | $checked = checked( $checked_value, $value, false ); 117 | $filtered_label = apply_filters( 'woocommerce_variation_option_name', $label, esc_attr( $name ) ); 118 | 119 | printf( 120 | '
121 | 122 | 123 |
', 124 | $input_name, $esc_value, $id, $checked, $filtered_label 125 | ); 126 | } 127 | } 128 | } 129 | --------------------------------------------------------------------------------