diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000000..47de40687da
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,9 @@
+{
+ "presets": [
+ "es2015",
+ "stage-2"
+ ],
+ "plugins": [
+ "add-module-exports"
+ ]
+}
diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 00000000000..a11de13a5c8
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,37 @@
+engines:
+ phpcodesniffer:
+ enabled: true
+ config:
+ standard: "WordPress"
+ eslint:
+ enabled: true
+ scss-lint:
+ enabled: true
+ duplication:
+ enabled: true
+ config:
+ languages:
+ - php
+ - javascript
+ratings:
+ paths:
+ - "includes/*"
+exclude_paths:
+ - tests/*
+ - apigen/*
+ - dummy-data/*
+ - i18n/*
+ - includes/api/legacy/*
+ - includes/libraries/*
+ - includes/updates/*
+ - includes/gateways/simplify-commerce/*
+ - includes/shipping/legacy-*
+ - includes/wc-deprecated-functions.php
+ - includes/class-wc-legacy-api.php
+ - assets/js/accounting/**
+ - assets/js/jquery-*
+ - assets/js/prettyPhoto/*
+ - assets/js/round/*
+ - assets/js/select2/*
+ - assets/js/stupidtable/*
+ - assets/js/zeroclipboard/*
diff --git a/.editorconfig b/.editorconfig
index d3b0b247108..c3dfa83750f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -2,7 +2,7 @@
# editorconfig.org
# WordPress Coding Standards
-# http://make.wordpress.org/core/handbook/coding-standards/
+# https://make.wordpress.org/core/handbook/coding-standards/
root = true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000000..5e8686b5f3a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,10 @@
+/.* export-ignore
+apigen* export-ignore
+CHANGELOG.txt export-ignore
+composer.* export-ignore
+Gruntfile.js export-ignore
+package.json export-ignore
+phpcs.ruleset.xml export-ignore
+phpunit.* export-ignore
+README.md export-ignore
+tests export-ignore
diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 83%
rename from CONTRIBUTING.md
rename to .github/CONTRIBUTING.md
index 689ae2a2296..97fcc742bbd 100644
--- a/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -6,23 +6,23 @@ When contributing please ensure you follow the guidelines below to help us keep
__Please Note:__
-GitHub is for _bug reports and contributions only_ - if you have a support question or a request for a customization this is not the right place to post it. Use [WooThemes Support](http://support.woothemes.com) for customer support, [WordPress.org](http://wordpress.org/support/plugin/woocommerce) for community support, and for customizations we recommend one of the following services:
+GitHub is for _bug reports and contributions only_ - if you have a support question or a request for a customization this is not the right place to post it. Use [WooCommerce Support](https://support.woocommerce.com) for customer support, [WordPress.org](https://wordpress.org/support/plugin/woocommerce) for community support, and for customizations we recommend one of the following services:
-- [WooExperts](http://www.woothemes.com/experts/)
+- [WooExperts](https://woocommerce.com/experts/)
- [Codeable](https://codeable.io/)
## Contributing To The Core
### Reporting Issues
-Reporting issues is a great way to became a contributor as it doesn't require technical skills. In fact you don't even need to know a programming language or to be able to check the code itself, you just need to make sure that everything works as expected and [submit an issue report](https://github.com/woothemes/woocommerce/issues/new) if you spot a bug. Sound like something you're up for? Go for it!
+Reporting issues is a great way to became a contributor as it doesn't require technical skills. In fact you don't even need to know a programming language or to be able to check the code itself, you just need to make sure that everything works as expected and [submit an issue report](https://github.com/woocommerce/woocommerce/issues/new) if you spot a bug. Sound like something you're up for? Go for it!
#### How To Submit An Issue Report
If something isn't working, congratulations you've found a bug! Help us fix it by submitting an issue report:
* Make sure you have a [GitHub account](https://github.com/signup/free)
-* Search the [Existing Issues](https://github.com/woothemes/woocommerce/issues) to be sure that the one you've noticed isn't already there
+* Search the [Existing Issues](https://github.com/woocommerce/woocommerce/issues) to be sure that the one you've noticed isn't already there
* Submit a report for your issue
* Clearly describe the issue (including steps to reproduce it if it's a bug)
* Make sure you fill in the earliest version that you know has the issue.
@@ -37,7 +37,7 @@ If you think something could be improved and you're able to do so, make your cha
* Fork the repository on GitHub
* Make the changes to your forked repository
- * **Ensure you stick to the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/php/).**
+ * **Ensure you stick to the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/coding-standards/php/).**
* Ensure you use LF line endings - no crazy Windows line endings please :)
* When committing, reference your issue number (#1234) and include a note about the fix
* Push the changes to your fork and submit a pull request on the master branch of the WooCommerce repository. Existing maintenance branches will be maintained by WooCommerce developers
@@ -86,14 +86,14 @@ If WooCommerce is already 100% translated for your language, join the team anywa
### Translating Video Tutorials
-Another valuable way to help is by translating our growing library of WooCommerce video tutorials. Check out our [Video SRTs project](https://www.transifex.com/projects/p/video-srts/) and join the team for your language. If there isn't one, please request the new language and we will add it for you.
+Another valuable way to help is by translating our growing library of WooCommerce video tutorials. Check out the [Translating Our Videos](https://docs.woocommerce.com/document/translating-our-videos/) doc and join in!
By translating video tutorials you'll be helping non-English speaking users and people affected by disabilities to get to grips with using WooCommerce for the first time, and to go on and create their businesses and make a living! That's something to be proud of and if you choose to dive into this area, we salute you.
# Additional Resources
-* [General GitHub documentation](http://help.github.com/)
-* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
+* [General GitHub documentation](https://help.github.com/)
+* [GitHub pull request documentation](https://help.github.com/articles/about-pull-requests/)
* [Translator Handbook](https://make.wordpress.org/polyglots/handbook/)
-* [WooCommerce Docs](http://docs.woothemes.com/)
-* [WooThemes Support](http://support.woothemes.com)
+* [WooCommerce Docs](https://docs.woocommerce.com/)
+* [WooCommerce Support](https://support.woocommerce.com)
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000000..0d1135a372b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,31 @@
+### EXPLANATION OF THE ISSUE
+
+
+
+### STEPS TO REPRODUCE THE ISSUE
+
+
+
+### SYSTEM STATUS
+
+ ' + value + ' "+b+" ' + wc_add_to_cart_variation_params.i18n_no_matching_variations_text + ' ' + wc_add_to_cart_variation_params.i18n_no_matching_variations_text + ' ' + wc_add_to_cart_variation_params.i18n_no_matching_variations_text + ' '+wc_add_to_cart_variation_params.i18n_no_matching_variations_text+" '+wc_add_to_cart_variation_params.i18n_no_matching_variations_text+" '+wc_add_to_cart_variation_params.i18n_no_matching_variations_text+"'+e.search_text+""};e.prototype.result_add_group=function(e){return!e.search_match&&!e.group_match?"":e.active_options>0?'0)return this.keydown_backstroke();if(!this.pending_backstroke){this.result_clear_highlight();return this.results_search()}break;case 13:e.preventDefault();if(this.results_showing)return this.result_select(e);break;case 27:this.results_showing&&this.results_hide();return!0;case 9:case 38:case 40:case 16:case 91:case 17:break;default:return this.results_search()}};e.prototype.container_width=function(){return this.options.width!=null?this.options.width:""+this.form_field.offsetWidth+"px"};e.prototype.include_option_in_results=function(e){return this.is_multiple&&!this.display_selected_options&&e.selected?!1:!this.display_disabled_options&&e.disabled?!1:e.empty?!1:!0};e.browser_is_supported=function(){return window.navigator.appName==="Microsoft Internet Explorer"?document.documentMode>=8:/iP(od|hone)/i.test(window.navigator.userAgent)?!1:/Android/i.test(window.navigator.userAgent)&&/Mobile/i.test(window.navigator.userAgent)?!1:!0};e.default_multiple_text="Select Some Options";e.default_single_text="Select an Option";e.default_no_result_text="No results match";return e}();e=jQuery;e.fn.extend({chosen:function(r){return t.browser_is_supported()?this.each(function(t){var i,s;i=e(this);s=i.data("chosen");r==="destroy"&&s?s.destroy():s||i.data("chosen",new n(this,r))}):this}});n=function(t){function n(){i=n.__super__.constructor.apply(this,arguments);return i}o(n,t);n.prototype.setup=function(){this.form_field_jq=e(this.form_field);this.current_selectedIndex=this.form_field.selectedIndex;return this.is_rtl=this.form_field_jq.hasClass("chosen-rtl")};n.prototype.set_up_html=function(){var t,n;t=["chosen-container"];t.push("chosen-container-"+(this.is_multiple?"multi":"single"));this.inherit_select_classes&&this.form_field.className&&t.push(this.form_field.className);this.is_rtl&&t.push("chosen-rtl");n={"class":t.join(" "),style:"width: "+this.container_width()+";",title:this.form_field.title};this.form_field.id.length&&(n.id=this.form_field.id.replace(/[^\w]/g,"_")+"_chosen");this.container=e("",n);this.is_multiple?this.container.html('0)return this.form_field_label.bind("click.chosen",function(e){return t.is_multiple?t.container_mousedown(e):t.activate_field()})};n.prototype.show_search_field_default=function(){if(this.is_multiple&&this.choices_count()<1&&!this.active_field){this.search_field.val(this.default_text);return this.search_field.addClass("default")}this.search_field.val("");return this.search_field.removeClass("default")};n.prototype.search_results_mouseup=function(t){var n;n=e(t.target).hasClass("active-result")?e(t.target):e(t.target).parents(".active-result").first();if(n.length){this.result_highlight=n;this.result_select(t);return this.search_field.focus()}};n.prototype.search_results_mouseover=function(t){var n;n=e(t.target).hasClass("active-result")?e(t.target):e(t.target).parents(".active-result").first();if(n)return this.result_do_highlight(n)};n.prototype.search_results_mouseout=function(t){if(e(t.target).hasClass("active-result"))return this.result_clear_highlight()};n.prototype.choice_build=function(t){var n,r,i=this;n=e("",{"class":"search-choice"}).html(""+t.html+"");if(t.disabled)n.addClass("search-choice-disabled");else{r=e("",{"class":"search-choice-close","data-option-array-index":t.array_index});r.bind("click.chosen",function(e){return i.choice_destroy_link_click(e)});n.append(r)}return this.search_container.before(n)};n.prototype.choice_destroy_link_click=function(t){t.preventDefault();t.stopPropagation();if(!this.is_disabled)return this.choice_destroy(e(t.target))};n.prototype.choice_destroy=function(e){if(this.result_deselect(e[0].getAttribute("data-option-array-index"))){this.show_search_field_default();this.is_multiple&&this.choices_count()>0&&this.search_field.val().length<1&&this.results_hide();e.parents("li").first().remove();return this.search_field_scale()}};n.prototype.results_reset=function(){this.form_field.options[0].selected=!0;this.selected_option_count=null;this.single_set_selected_text();this.show_search_field_default();this.results_reset_cleanup();this.form_field_jq.trigger("change");if(this.active_field)return this.results_hide()};n.prototype.results_reset_cleanup=function(){this.current_selectedIndex=this.form_field.selectedIndex;return this.selected_item.find("abbr").remove()};n.prototype.result_select=function(e){var t,n,r;if(this.result_highlight){t=this.result_highlight;this.result_clear_highlight();if(this.is_multiple&&this.max_selected_options<=this.choices_count()){this.form_field_jq.trigger("chosen:maxselected",{chosen:this});return!1}if(this.is_multiple)t.removeClass("active-result");else{if(this.result_single_selected){this.result_single_selected.removeClass("result-selected");r=this.result_single_selected[0].getAttribute("data-option-array-index");this.results_data[r].selected=!1}this.result_single_selected=t}t.addClass("result-selected");n=this.results_data[t[0].getAttribute("data-option-array-index")];n.selected=!0;this.form_field.options[n.options_index].selected=!0;this.selected_option_count=null;this.is_multiple?this.choice_build(n):this.single_set_selected_text(n.text);(!e.metaKey&&!e.ctrlKey||!this.is_multiple)&&this.results_hide();this.search_field.val("");(this.is_multiple||this.form_field.selectedIndex!==this.current_selectedIndex)&&this.form_field_jq.trigger("change",{selected:this.form_field.options[n.options_index].value});this.current_selectedIndex=this.form_field.selectedIndex;return this.search_field_scale()}};n.prototype.single_set_selected_text=function(e){e==null&&(e=this.default_text);if(e===this.default_text)this.selected_item.addClass("chosen-default");else{this.single_deselect_control_build();this.selected_item.removeClass("chosen-default")}return this.selected_item.find("span").text(e)};n.prototype.result_deselect=function(e){var t;t=this.results_data[e];if(!this.form_field.options[t.options_index].disabled){t.selected=!1;this.form_field.options[t.options_index].selected=!1;this.selected_option_count=null;this.result_clear_highlight();this.results_showing&&this.winnow_results();this.form_field_jq.trigger("change",{deselected:this.form_field.options[t.options_index].value});this.search_field_scale();return!0}return!1};n.prototype.single_deselect_control_build=function(){if(!this.allow_single_deselect)return;this.selected_item.find("abbr").length||this.selected_item.find("span").first().after('');return this.selected_item.addClass("chosen-single-with-deselect")};n.prototype.get_search_text=function(){return this.search_field.val()===this.default_text?"":e("").text(e.trim(this.search_field.val())).html()};n.prototype.winnow_results_set_highlight=function(){var e,t;t=this.is_multiple?[]:this.search_results.find(".result-selected.active-result");e=t.length?t.first():this.search_results.find(".active-result").first();if(e!=null)return this.result_do_highlight(e)};n.prototype.no_results=function(t){var n;n=e('');
+
+ if (slider.pagingCount > 1) {
+ for (var i = 0; i < slider.pagingCount; i++) {
+ slide = slider.slides.eq(i);
+ if ( undefined === slide.attr( 'data-thumb-alt' ) ) { slide.attr( 'data-thumb-alt', '' ); }
+ var altText = ( '' !== slide.attr( 'data-thumb-alt' ) ) ? altText = ' alt="' + slide.attr( 'data-thumb-alt' ) + '"' : '';
+ item = (slider.vars.controlNav === "thumbnails") ? '' : '' + j + '';
+ if ( 'thumbnails' === slider.vars.controlNav && true === slider.vars.thumbCaptions ) {
+ var captn = slide.attr( 'data-thumbcaption' );
+ if ( '' !== captn && undefined !== captn ) { item += ' '; }
+ }
+ slider.controlNavScaffold.append('
"+b+"
"),c&&f.append(""+c+"
"),void 0===d&&(d=3e3);var g=function(b){b=b||{},a.blockUI({message:f,fadeIn:"undefined"!=typeof b.fadeIn?b.fadeIn:700,fadeOut:"undefined"!=typeof b.fadeOut?b.fadeOut:1e3,timeout:"undefined"!=typeof b.timeout?b.timeout:d,centerY:!1,showOverlay:!1,onUnblock:e,css:a.blockUI.defaults.growlCSS})};g();f.css("opacity");f.mouseover(function(){g({fadeIn:0,timeout:3e4});var b=a(".blockMsg");b.stop(),b.fadeTo(300,1)}).mouseout(function(){a(".blockMsg").fadeOut(1e3)})},a.fn.block=function(c){if(this[0]===window)return a.blockUI(c),this;var d=a.extend({},a.blockUI.defaults,c||{});return this.each(function(){var b=a(this);d.ignoreIfBlocked&&b.data("blockUI.isBlocked")||b.unblock({fadeOut:0})}),this.each(function(){"static"==a.css(this,"position")&&(this.style.position="relative",a(this).data("blockUI.static",!0)),this.style.zoom=1,b(this,c)})},a.fn.unblock=function(b){return this[0]===window?(a.unblockUI(b),this):this.each(function(){c(this,b)})},a.blockUI.version=2.7,a.blockUI.defaults={message:"Please wait...
",title:null,draggable:!0,theme:!1,css:{padding:0,margin:0,width:"30%",top:"40%",left:"35%",textAlign:"center",color:"#000",border:"3px solid #aaa",backgroundColor:"#fff",cursor:"wait"},themedCSS:{width:"30%",top:"40%",left:"35%"},overlayCSS:{backgroundColor:"#000",opacity:.6,cursor:"wait"},cursorReset:"default",growlCSS:{width:"350px",top:"10px",left:"",right:"10px",border:"none",padding:"5px",opacity:.6,cursor:"default",color:"#fff",backgroundColor:"#000","-webkit-border-radius":"10px","-moz-border-radius":"10px","border-radius":"10px"},iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank",forceIframe:!1,baseZ:1e3,centerX:!0,centerY:!0,allowBodyStretch:!0,bindEvents:!0,constrainTabKey:!0,fadeIn:200,fadeOut:400,timeout:0,showOverlay:!0,focusInput:!0,focusableElements:":input:enabled:visible",onBlock:null,onUnblock:null,onOverlayClick:null,quirksmodeOffsetHack:4,blockMsgClass:"blockMsg",ignoreIfBlocked:!1};var n=null,o=[]}"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],a):a(jQuery)}();
\ No newline at end of file
+!function(){"use strict";function a(a){function b(b,d){var f,p,q=b==window,r=d&&void 0!==d.message?d.message:void 0;if(d=a.extend({},a.blockUI.defaults,d||{}),!d.ignoreIfBlocked||!a(b).data("blockUI.isBlocked")){if(d.overlayCSS=a.extend({},a.blockUI.defaults.overlayCSS,d.overlayCSS||{}),f=a.extend({},a.blockUI.defaults.css,d.css||{}),d.onOverlayClick&&(d.overlayCSS.cursor="pointer"),p=a.extend({},a.blockUI.defaults.themedCSS,d.themedCSS||{}),r=void 0===r?d.message:r,q&&n&&c(window,{fadeOut:0}),r&&"string"!=typeof r&&(r.parentNode||r.jquery)){var s=r.jquery?r[0]:r,t={};a(b).data("blockUI.history",t),t.el=s,t.parent=s.parentNode,t.display=s.style.display,t.position=s.style.position,t.parent&&t.parent.removeChild(s)}a(b).data("blockUI.onUnblock",d.onUnblock);var u,v,w,x,y=d.baseZ;u=a(k||d.forceIframe?'':''),v=a(d.theme?'':''),d.theme&&q?(x=' "):d.theme?(x=' "):x=q?'':'',w=a(x),r&&(d.theme?(w.css(p),w.addClass("ui-widget-content")):w.css(f)),d.theme||v.css(d.overlayCSS),v.css("position",q?"fixed":"absolute"),(k||d.forceIframe)&&u.css("opacity",0);var z=[u,v,w],A=a(q?"body":b);a.each(z,function(){this.appendTo(A)}),d.theme&&d.draggable&&a.fn.draggable&&w.draggable({handle:".ui-dialog-titlebar",cancel:"li"});var B=m&&(!a.support.boxModel||a("object,embed",q?null:b).length>0);if(l||B){if(q&&d.allowBodyStretch&&a.support.boxModel&&a("html,body").css("height","100%"),(l||!a.support.boxModel)&&!q)var C=i(b,"borderTopWidth"),D=i(b,"borderLeftWidth"),E=C?"(0 - "+C+")":0,F=D?"(0 - "+D+")":0;a.each(z,function(a,b){var c=b[0].style;if(c.position="absolute",a<2)q?c.setExpression("height","Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:"+d.quirksmodeOffsetHack+') + "px"'):c.setExpression("height",'this.parentNode.offsetHeight + "px"'),q?c.setExpression("width",'jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'):c.setExpression("width",'this.parentNode.offsetWidth + "px"'),F&&c.setExpression("left",F),E&&c.setExpression("top",E);else if(d.centerY)q&&c.setExpression("top",'(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'),c.marginTop=0;else if(!d.centerY&&q){var e=d.css&&d.css.top?parseInt(d.css.top,10):0,f="((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "+e+') + "px"';c.setExpression("top",f)}})}if(r&&(d.theme?w.find(".ui-widget-content").append(r):w.append(r),(r.jquery||r.nodeType)&&a(r).show()),(k||d.forceIframe)&&d.showOverlay&&u.show(),d.fadeIn){var G=d.onBlock?d.onBlock:j,H=d.showOverlay&&!r?G:j,I=r?G:j;d.showOverlay&&v._fadeIn(d.fadeIn,H),r&&w._fadeIn(d.fadeIn,I)}else d.showOverlay&&v.show(),r&&w.show(),d.onBlock&&d.onBlock.bind(w)();if(e(1,b,d),q?(n=w[0],o=a(d.focusableElements,n),d.focusInput&&setTimeout(g,20)):h(w[0],d.centerX,d.centerY),d.timeout){var J=setTimeout(function(){q?a.unblockUI(d):a(b).unblock(d)},d.timeout);a(b).data("blockUI.timeout",J)}}}function c(b,c){var f,g=b==window,h=a(b),i=h.data("blockUI.history"),j=h.data("blockUI.timeout");j&&(clearTimeout(j),h.removeData("blockUI.timeout")),c=a.extend({},a.blockUI.defaults,c||{}),e(0,b,c),null===c.onUnblock&&(c.onUnblock=h.data("blockUI.onUnblock"),h.removeData("blockUI.onUnblock"));var k;k=g?a(document.body).children().filter(".blockUI").add("body > .blockUI"):h.find(">.blockUI"),c.cursorReset&&(k.length>1&&(k[1].style.cursor=c.cursorReset),k.length>2&&(k[2].style.cursor=c.cursorReset)),g&&(n=o=null),c.fadeOut?(f=k.length,k.stop().fadeOut(c.fadeOut,function(){0===--f&&d(k,i,c,b)})):d(k,i,c,b)}function d(b,c,d,e){var f=a(e);if(!f.data("blockUI.isBlocked")){b.each(function(a,b){this.parentNode&&this.parentNode.removeChild(this)}),c&&c.el&&(c.el.style.display=c.display,c.el.style.position=c.position,c.el.style.cursor="default",c.parent&&c.parent.appendChild(c.el),f.removeData("blockUI.history")),f.data("blockUI.static")&&f.css("position","static"),"function"==typeof d.onUnblock&&d.onUnblock(e,d);var g=a(document.body),h=g.width(),i=g[0].style.width;g.width(h-1).width(h),g[0].style.width=i}}function e(b,c,d){var e=c==window,g=a(c);if((b||(!e||n)&&(e||g.data("blockUI.isBlocked")))&&(g.data("blockUI.isBlocked",b),e&&d.bindEvents&&(!b||d.showOverlay))){var h="mousedown mouseup keydown keypress keyup touchstart touchend touchmove";b?a(document).bind(h,d,f):a(document).unbind(h,f)}}function f(b){if("keydown"===b.type&&b.keyCode&&9==b.keyCode&&n&&b.data.constrainTabKey){var c=o,d=!b.shiftKey&&b.target===c[c.length-1],e=b.shiftKey&&b.target===c[0];if(d||e)return setTimeout(function(){g(e)},10),!1}var f=b.data,h=a(b.target);return h.hasClass("blockOverlay")&&f.onOverlayClick&&f.onOverlayClick(b),h.parents("div."+f.blockMsgClass).length>0||0===h.parents().children().filter("div.blockUI").length}function g(a){if(o){var b=o[a===!0?o.length-1:0];b&&b.focus()}}function h(a,b,c){var d=a.parentNode,e=a.style,f=(d.offsetWidth-a.offsetWidth)/2-i(d,"borderLeftWidth"),g=(d.offsetHeight-a.offsetHeight)/2-i(d,"borderTopWidth");b&&(e.left=f>0?f+"px":"0"),c&&(e.top=g>0?g+"px":"0")}function i(b,c){return parseInt(a.css(b,c),10)||0}a.fn._fadeIn=a.fn.fadeIn;var j=a.noop||function(){},k=/MSIE/.test(navigator.userAgent),l=/MSIE 6.0/.test(navigator.userAgent)&&!/MSIE 8.0/.test(navigator.userAgent),m=(document.documentMode||0,a.isFunction(document.createElement("div").style.setExpression));a.blockUI=function(a){b(window,a)},a.unblockUI=function(a){c(window,a)},a.growlUI=function(b,c,d,e){var f=a('');b&&f.append(""+b+"
"),c&&f.append(""+c+"
"),void 0===d&&(d=3e3);var g=function(b){b=b||{},a.blockUI({message:f,fadeIn:"undefined"!=typeof b.fadeIn?b.fadeIn:700,fadeOut:"undefined"!=typeof b.fadeOut?b.fadeOut:1e3,timeout:"undefined"!=typeof b.timeout?b.timeout:d,centerY:!1,showOverlay:!1,onUnblock:e,css:a.blockUI.defaults.growlCSS})};g();f.css("opacity");f.mouseover(function(){g({fadeIn:0,timeout:3e4});var b=a(".blockMsg");b.stop(),b.fadeTo(300,1)}).mouseout(function(){a(".blockMsg").fadeOut(1e3)})},a.fn.block=function(c){if(this[0]===window)return a.blockUI(c),this;var d=a.extend({},a.blockUI.defaults,c||{});return this.each(function(){var b=a(this);d.ignoreIfBlocked&&b.data("blockUI.isBlocked")||b.unblock({fadeOut:0})}),this.each(function(){"static"==a.css(this,"position")&&(this.style.position="relative",a(this).data("blockUI.static",!0)),this.style.zoom=1,b(this,c)})},a.fn.unblock=function(b){return this[0]===window?(a.unblockUI(b),this):this.each(function(){c(this,b)})},a.blockUI.version=2.7,a.blockUI.defaults={message:"Please wait...
",title:null,draggable:!0,theme:!1,css:{padding:0,margin:0,width:"30%",top:"40%",left:"35%",textAlign:"center",color:"#000",border:"3px solid #aaa",backgroundColor:"#fff",cursor:"wait"},themedCSS:{width:"30%",top:"40%",left:"35%"},overlayCSS:{backgroundColor:"#000",opacity:.6,cursor:"wait"},cursorReset:"default",growlCSS:{width:"350px",top:"10px",left:"",right:"10px",border:"none",padding:"5px",opacity:.6,cursor:"default",color:"#fff",backgroundColor:"#000","-webkit-border-radius":"10px","-moz-border-radius":"10px","border-radius":"10px"},iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank",forceIframe:!1,baseZ:1e3,centerX:!0,centerY:!0,allowBodyStretch:!0,bindEvents:!0,constrainTabKey:!0,fadeIn:200,fadeOut:400,timeout:0,showOverlay:!0,focusInput:!0,focusableElements:":input:enabled:visible",onBlock:null,onUnblock:null,onOverlayClick:null,quirksmodeOffsetHack:4,blockMsgClass:"blockMsg",ignoreIfBlocked:!1};var n=null,o=[]}"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],a):a(jQuery)}();
\ No newline at end of file
diff --git a/assets/js/jquery-cookie/jquery.cookie.min.js b/assets/js/jquery-cookie/jquery.cookie.min.js
index 5be813ad400..10486ae1815 100644
--- a/assets/js/jquery-cookie/jquery.cookie.min.js
+++ b/assets/js/jquery-cookie/jquery.cookie.min.js
@@ -5,4 +5,4 @@
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
-!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});
\ No newline at end of file
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(a){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;n"),g=!0),e.push(' "),0!=e.length){var l=''+k.label+" ")}if(g&&e.push("'+e.join("")+"
";if(null!=ea.legend.container)a(ea.legend.container).html(l);else{var m="",n=ea.legend.position,o=ea.legend.margin;null==o[0]&&(o=[o,o]),"n"==n.charAt(0)?m+="top:"+(o[1]+ma.top)+"px;":"s"==n.charAt(0)&&(m+="bottom:"+(o[1]+ma.bottom)+"px;"),"e"==n.charAt(1)?m+="right:"+(o[0]+ma.right)+"px;":"w"==n.charAt(1)&&(m+="left:"+(o[0]+ma.left)+"px;");var p=a('"),g=!0),e.push(' "),0!=e.length){var l=''+k.label+" ")}if(g&&e.push("'+e.join("")+"
";if(null!=ea.legend.container)a(ea.legend.container).html(l);else{var m="",n=ea.legend.position,o=ea.legend.margin;null==o[0]&&(o=[o,o]),"n"==n.charAt(0)?m+="top:"+(o[1]+ma.top)+"px;":"s"==n.charAt(0)&&(m+="bottom:"+(o[1]+ma.bottom)+"px;"),"e"==n.charAt(1)?m+="right:"+(o[0]+ma.right)+"px;":"w"==n.charAt(1)&&(m+="left:"+(o[0]+ma.left)+"px;");var p=a('
"+Math.round(b.percent)+"%
"+Math.round(b.percent)+"%
').css({'width':settings.default_width}).wrapInner('
').css({width:settings.default_width}).wrapInner(' \t\t\t\t\t\t\t\t\t\t{gallery} \t\t\t\t\t\t\t\t\t
\t\t\t\t\t\t\t\t
').css({width:settings.default_width}).wrapInner('', {
+ 'class': 'select2-results__options select2-results__options--nested'
+ });
+
+ $childrenContainer.append($children);
+
+ $option.append(label);
+ $option.append($childrenContainer);
+ } else {
+ this.template(data, option);
+ }
+
+ $.data(option, 'data', data);
+
+ return option;
+ };
+
+ Results.prototype.bind = function (container, $container) {
+ var self = this;
+
+ var id = container.id + '-results';
+
+ this.$results.attr('id', id);
+
+ container.on('results:all', function (params) {
+ self.clear();
+ self.append(params.data);
+
+ if (container.isOpen()) {
+ self.setClasses();
+ self.highlightFirstItem();
+ }
+ });
+
+ container.on('results:append', function (params) {
+ self.append(params.data);
+
+ if (container.isOpen()) {
+ self.setClasses();
+ }
+ });
+
+ container.on('query', function (params) {
+ self.hideMessages();
+ self.showLoading(params);
+ });
+
+ container.on('select', function () {
+ if (!container.isOpen()) {
+ return;
+ }
+
+ self.setClasses();
+ self.highlightFirstItem();
+ });
+
+ container.on('unselect', function () {
+ if (!container.isOpen()) {
+ return;
+ }
+
+ self.setClasses();
+ self.highlightFirstItem();
+ });
+
+ container.on('open', function () {
+ // When the dropdown is open, aria-expended="true"
+ self.$results.attr('aria-expanded', 'true');
+ self.$results.attr('aria-hidden', 'false');
+
+ self.setClasses();
+ self.ensureHighlightVisible();
+ });
+
+ container.on('close', function () {
+ // When the dropdown is closed, aria-expended="false"
+ self.$results.attr('aria-expanded', 'false');
+ self.$results.attr('aria-hidden', 'true');
+ self.$results.removeAttr('aria-activedescendant');
+ });
+
+ container.on('results:toggle', function () {
+ var $highlighted = self.getHighlightedResults();
+
+ if ($highlighted.length === 0) {
+ return;
+ }
+
+ $highlighted.trigger('mouseup');
+ });
+
+ container.on('results:select', function () {
+ var $highlighted = self.getHighlightedResults();
+
+ if ($highlighted.length === 0) {
+ return;
+ }
+
+ var data = $highlighted.data('data');
+
+ if ($highlighted.attr('aria-selected') == 'true') {
+ self.trigger('close', {});
+ } else {
+ self.trigger('select', {
+ data: data
+ });
+ }
+ });
+
+ container.on('results:previous', function () {
+ var $highlighted = self.getHighlightedResults();
+
+ var $options = self.$results.find('[aria-selected]');
+
+ var currentIndex = $options.index($highlighted);
+
+ // If we are already at te top, don't move further
+ if (currentIndex === 0) {
+ return;
+ }
+
+ var nextIndex = currentIndex - 1;
+
+ // If none are highlighted, highlight the first
+ if ($highlighted.length === 0) {
+ nextIndex = 0;
+ }
+
+ var $next = $options.eq(nextIndex);
+
+ $next.trigger('mouseenter');
+
+ var currentOffset = self.$results.offset().top;
+ var nextTop = $next.offset().top;
+ var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
+
+ if (nextIndex === 0) {
+ self.$results.scrollTop(0);
+ } else if (nextTop - currentOffset < 0) {
+ self.$results.scrollTop(nextOffset);
+ }
+ });
+
+ container.on('results:next', function () {
+ var $highlighted = self.getHighlightedResults();
+
+ var $options = self.$results.find('[aria-selected]');
+
+ var currentIndex = $options.index($highlighted);
+
+ var nextIndex = currentIndex + 1;
+
+ // If we are at the last option, stay there
+ if (nextIndex >= $options.length) {
+ return;
+ }
+
+ var $next = $options.eq(nextIndex);
+
+ $next.trigger('mouseenter');
+
+ var currentOffset = self.$results.offset().top +
+ self.$results.outerHeight(false);
+ var nextBottom = $next.offset().top + $next.outerHeight(false);
+ var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
+
+ if (nextIndex === 0) {
+ self.$results.scrollTop(0);
+ } else if (nextBottom > currentOffset) {
+ self.$results.scrollTop(nextOffset);
+ }
+ });
+
+ container.on('results:focus', function (params) {
+ params.element.addClass('select2-results__option--highlighted');
+ });
+
+ container.on('results:message', function (params) {
+ self.displayMessage(params);
+ });
+
+ if ($.fn.mousewheel) {
+ this.$results.on('mousewheel', function (e) {
+ var top = self.$results.scrollTop();
+
+ var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
+
+ var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
+ var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
+
+ if (isAtTop) {
+ self.$results.scrollTop(0);
+
+ e.preventDefault();
+ e.stopPropagation();
+ } else if (isAtBottom) {
+ self.$results.scrollTop(
+ self.$results.get(0).scrollHeight - self.$results.height()
+ );
+
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ });
+ }
+
+ this.$results.on('mouseup', '.select2-results__option[aria-selected]',
+ function (evt) {
+ var $this = $(this);
+
+ var data = $this.data('data');
+
+ if ($this.attr('aria-selected') === 'true') {
+ if (self.options.get('multiple')) {
+ self.trigger('unselect', {
+ originalEvent: evt,
+ data: data
+ });
+ } else {
+ self.trigger('close', {});
+ }
+
+ return;
+ }
+
+ self.trigger('select', {
+ originalEvent: evt,
+ data: data
+ });
+ });
+
+ this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
+ function (evt) {
+ var data = $(this).data('data');
+
+ self.getHighlightedResults()
+ .removeClass('select2-results__option--highlighted');
+
+ self.trigger('results:focus', {
+ data: data,
+ element: $(this)
+ });
+ });
+ };
+
+ Results.prototype.getHighlightedResults = function () {
+ var $highlighted = this.$results
+ .find('.select2-results__option--highlighted');
+
+ return $highlighted;
+ };
+
+ Results.prototype.destroy = function () {
+ this.$results.remove();
+ };
+
+ Results.prototype.ensureHighlightVisible = function () {
+ var $highlighted = this.getHighlightedResults();
+
+ if ($highlighted.length === 0) {
+ return;
+ }
+
+ var $options = this.$results.find('[aria-selected]');
+
+ var currentIndex = $options.index($highlighted);
+
+ var currentOffset = this.$results.offset().top;
+ var nextTop = $highlighted.offset().top;
+ var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
+
+ var offsetDelta = nextTop - currentOffset;
+ nextOffset -= $highlighted.outerHeight(false) * 2;
+
+ if (currentIndex <= 2) {
+ this.$results.scrollTop(0);
+ } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
+ this.$results.scrollTop(nextOffset);
+ }
+ };
+
+ Results.prototype.template = function (result, container) {
+ var template = this.options.get('templateResult');
+ var escapeMarkup = this.options.get('escapeMarkup');
+
+ var content = template(result, container);
+
+ if (content == null) {
+ container.style.display = 'none';
+ } else if (typeof content === 'string') {
+ container.innerHTML = escapeMarkup(content);
+ } else {
+ $(container).append(content);
+ }
+ };
+
+ return Results;
+});
+
+S2.define('select2/keys',[
+
+], function () {
+ var KEYS = {
+ BACKSPACE: 8,
+ TAB: 9,
+ ENTER: 13,
+ SHIFT: 16,
+ CTRL: 17,
+ ALT: 18,
+ ESC: 27,
+ SPACE: 32,
+ PAGE_UP: 33,
+ PAGE_DOWN: 34,
+ END: 35,
+ HOME: 36,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ DELETE: 46
+ };
+
+ return KEYS;
+});
+
+S2.define('select2/selection/base',[
+ 'jquery',
+ '../utils',
+ '../keys'
+], function ($, Utils, KEYS) {
+ function BaseSelection ($element, options) {
+ this.$element = $element;
+ this.options = options;
+
+ BaseSelection.__super__.constructor.call(this);
+ }
+
+ Utils.Extend(BaseSelection, Utils.Observable);
+
+ BaseSelection.prototype.render = function () {
+ var $selection = $(
+ '' +
+ ''
+ );
+
+ this._tabindex = 0;
+
+ if (this.$element.data('old-tabindex') != null) {
+ this._tabindex = this.$element.data('old-tabindex');
+ } else if (this.$element.attr('tabindex') != null) {
+ this._tabindex = this.$element.attr('tabindex');
+ }
+
+ $selection.attr('title', this.$element.attr('title'));
+ $selection.attr('tabindex', this._tabindex);
+
+ this.$selection = $selection;
+
+ return $selection;
+ };
+
+ BaseSelection.prototype.bind = function (container, $container) {
+ var self = this;
+
+ var id = container.id + '-container';
+ var resultsId = container.id + '-results';
+
+ this.container = container;
+
+ this.$selection.on('focus', function (evt) {
+ self.trigger('focus', evt);
+ });
+
+ this.$selection.on('blur', function (evt) {
+ self._handleBlur(evt);
+ });
+
+ this.$selection.on('keydown', function (evt) {
+ self.trigger('keypress', evt);
+
+ if (evt.which === KEYS.SPACE) {
+ evt.preventDefault();
+ }
+ });
+
+ container.on('results:focus', function (params) {
+ self.$selection.attr('aria-activedescendant', params.data._resultId);
+ });
+
+ container.on('selection:update', function (params) {
+ self.update(params.data);
+ });
+
+ container.on('open', function () {
+ // When the dropdown is open, aria-expanded="true"
+ self.$selection.attr('aria-expanded', 'true');
+ self.$selection.attr('aria-owns', resultsId);
+
+ self._attachCloseHandler(container);
+ });
+
+ container.on('close', function () {
+ // When the dropdown is closed, aria-expanded="false"
+ self.$selection.attr('aria-expanded', 'false');
+ self.$selection.removeAttr('aria-activedescendant');
+ self.$selection.removeAttr('aria-owns');
+
+ self.$selection.focus();
+
+ self._detachCloseHandler(container);
+ });
+
+ container.on('enable', function () {
+ self.$selection.attr('tabindex', self._tabindex);
+ });
+
+ container.on('disable', function () {
+ self.$selection.attr('tabindex', '-1');
+ });
+ };
+
+ BaseSelection.prototype._handleBlur = function (evt) {
+ var self = this;
+
+ // This needs to be delayed as the active element is the body when the tab
+ // key is pressed, possibly along with others.
+ window.setTimeout(function () {
+ // Don't trigger `blur` if the focus is still in the selection
+ if (
+ (document.activeElement == self.$selection[0]) ||
+ ($.contains(self.$selection[0], document.activeElement))
+ ) {
+ return;
+ }
+
+ self.trigger('blur', evt);
+ }, 1);
+ };
+
+ BaseSelection.prototype._attachCloseHandler = function (container) {
+ var self = this;
+
+ $(document.body).on('mousedown.select2.' + container.id, function (e) {
+ var $target = $(e.target);
+
+ var $select = $target.closest('.select2');
+
+ var $all = $('.select2.select2-container--open');
+
+ $all.each(function () {
+ var $this = $(this);
+
+ if (this == $select[0]) {
+ return;
+ }
+
+ var $element = $this.data('element');
+
+ $element.select2('close');
+ });
+ });
+ };
+
+ BaseSelection.prototype._detachCloseHandler = function (container) {
+ $(document.body).off('mousedown.select2.' + container.id);
+ };
+
+ BaseSelection.prototype.position = function ($selection, $container) {
+ var $selectionContainer = $container.find('.selection');
+ $selectionContainer.append($selection);
+ };
+
+ BaseSelection.prototype.destroy = function () {
+ this._detachCloseHandler(this.container);
+ };
+
+ BaseSelection.prototype.update = function (data) {
+ throw new Error('The `update` method must be defined in child classes.');
+ };
+
+ return BaseSelection;
+});
+
+S2.define('select2/selection/single',[
+ 'jquery',
+ './base',
+ '../utils',
+ '../keys'
+], function ($, BaseSelection, Utils, KEYS) {
+ function SingleSelection () {
+ SingleSelection.__super__.constructor.apply(this, arguments);
+ }
+
+ Utils.Extend(SingleSelection, BaseSelection);
+
+ SingleSelection.prototype.render = function () {
+ var $selection = SingleSelection.__super__.render.call(this);
+
+ $selection.addClass('select2-selection--single');
+
+ $selection.html(
+ '' +
+ '' +
+ '' +
+ ''
+ );
+
+ return $selection;
+ };
+
+ SingleSelection.prototype.bind = function (container, $container) {
+ var self = this;
+
+ SingleSelection.__super__.bind.apply(this, arguments);
+
+ var id = container.id + '-container';
+
+ this.$selection.find('.select2-selection__rendered').attr('id', id);
+ this.$selection.attr('aria-labelledby', id);
+
+ this.$selection.on('mousedown', function (evt) {
+ // Only respond to left clicks
+ if (evt.which !== 1) {
+ return;
+ }
+
+ self.trigger('toggle', {
+ originalEvent: evt
+ });
+ });
+
+ this.$selection.on('focus', function (evt) {
+ // User focuses on the container
+ });
+
+ this.$selection.on('blur', function (evt) {
+ // User exits the container
+ });
+
+ container.on('focus', function (evt) {
+ if (!container.isOpen()) {
+ self.$selection.focus();
+ }
+ });
+
+ container.on('selection:update', function (params) {
+ self.update(params.data);
+ });
+ };
+
+ SingleSelection.prototype.clear = function () {
+ this.$selection.find('.select2-selection__rendered').empty();
+ };
+
+ SingleSelection.prototype.display = function (data, container) {
+ var template = this.options.get('templateSelection');
+ var escapeMarkup = this.options.get('escapeMarkup');
+
+ return escapeMarkup(template(data, container));
+ };
+
+ SingleSelection.prototype.selectionContainer = function () {
+ return $('');
+ };
+
+ SingleSelection.prototype.update = function (data) {
+ if (data.length === 0) {
+ this.clear();
+ return;
+ }
+
+ var selection = data[0];
+
+ var $rendered = this.$selection.find('.select2-selection__rendered');
+ var formatted = this.display(selection, $rendered);
+
+ $rendered.empty().append(formatted);
+ $rendered.prop('title', selection.title || selection.text);
+ };
+
+ return SingleSelection;
+});
+
+S2.define('select2/selection/multiple',[
+ 'jquery',
+ './base',
+ '../utils'
+], function ($, BaseSelection, Utils) {
+ function MultipleSelection ($element, options) {
+ MultipleSelection.__super__.constructor.apply(this, arguments);
+ }
+
+ Utils.Extend(MultipleSelection, BaseSelection);
+
+ MultipleSelection.prototype.render = function () {
+ var $selection = MultipleSelection.__super__.render.call(this);
+
+ $selection.addClass('select2-selection--multiple');
+
+ $selection.html(
+ '
'
+ );
+
+ return $selection;
+ };
+
+ MultipleSelection.prototype.bind = function (container, $container) {
+ var self = this;
+
+ MultipleSelection.__super__.bind.apply(this, arguments);
+
+ this.$selection.on('click', function (evt) {
+ self.trigger('toggle', {
+ originalEvent: evt
+ });
+ });
+
+ this.$selection.on(
+ 'click',
+ '.select2-selection__choice__remove',
+ function (evt) {
+ // Ignore the event if it is disabled
+ if (self.options.get('disabled')) {
+ return;
+ }
+
+ var $remove = $(this);
+ var $selection = $remove.parent();
+
+ var data = $selection.data('data');
+
+ self.trigger('unselect', {
+ originalEvent: evt,
+ data: data
+ });
+ }
+ );
+ };
+
+ MultipleSelection.prototype.clear = function () {
+ this.$selection.find('.select2-selection__rendered').empty();
+ };
+
+ MultipleSelection.prototype.display = function (data, container) {
+ var template = this.options.get('templateSelection');
+ var escapeMarkup = this.options.get('escapeMarkup');
+
+ return escapeMarkup(template(data, container));
+ };
+
+ MultipleSelection.prototype.selectionContainer = function () {
+ var $container = $(
+ '
' . basename( $download_object->get_file() ) . '
', '' . implode( ', ', array_keys( $download_object->get_allowed_mime_types() ) ) . '
' );
+ }
+ continue;
+ }
+
+ // Validate the file exists.
+ if ( ! $download_object->file_exists() ) {
+ if ( $this->get_object_read() ) {
+ $errors[] = sprintf( __( 'The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ), '' . $download_object->get_file() . '
' );
+ }
+ continue;
+ }
+
+ $downloads[ $download_object->get_id() ] = $download_object;
+ }
+
+ if ( $errors ) {
+ $this->error( 'product_invalid_download', $errors[0] );
+ }
+
+ $this->set_prop( 'downloads', $downloads );
+ }
+
+ /**
+ * Set download limit.
+ *
+ * @since 3.0.0
+ * @param int $download_limit
+ */
+ public function set_download_limit( $download_limit ) {
+ $this->set_prop( 'download_limit', -1 === (int) $download_limit || '' === $download_limit ? -1 : absint( $download_limit ) );
+ }
+
+ /**
+ * Set download expiry.
+ *
+ * @since 3.0.0
+ * @param int $download_expiry
+ */
+ public function set_download_expiry( $download_expiry ) {
+ $this->set_prop( 'download_expiry', -1 === (int) $download_expiry || '' === $download_expiry ? -1 : absint( $download_expiry ) );
+ }
+
+ /**
+ * Set gallery attachment ids.
+ *
+ * @since 3.0.0
+ * @param array $image_ids
+ */
+ public function set_gallery_image_ids( $image_ids ) {
+ $image_ids = wp_parse_id_list( $image_ids );
+
+ if ( $this->get_object_read() ) {
+ $image_ids = array_filter( $image_ids, 'wp_attachment_is_image' );
+ }
+
+ $this->set_prop( 'gallery_image_ids', $image_ids );
+ }
+
+ /**
+ * Set main image ID.
+ *
+ * @since 3.0.0
+ * @param int $image_id
+ */
+ public function set_image_id( $image_id = '' ) {
+ $this->set_prop( 'image_id', $image_id );
+ }
+
+ /**
+ * Set rating counts. Read only.
+ * @param array $counts
+ */
+ public function set_rating_counts( $counts ) {
+ $this->set_prop( 'rating_counts', array_filter( array_map( 'absint', (array) $counts ) ) );
+ }
+
+ /**
+ * Set average rating. Read only.
+ * @param float $average
+ */
+ public function set_average_rating( $average ) {
+ $this->set_prop( 'average_rating', wc_format_decimal( $average ) );
+ }
+
+ /**
+ * Set review count. Read only.
+ * @param int $count
+ */
+ public function set_review_count( $count ) {
+ $this->set_prop( 'review_count', absint( $count ) );
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | Other Methods
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Ensure properties are set correctly before save.
+ * @since 3.0.0
+ */
+ public function validate_props() {
+ // Before updating, ensure stock props are all aligned. Qty and backorders are not needed if not stock managed.
+ if ( ! $this->get_manage_stock() ) {
+ $this->set_stock_quantity( '' );
+ $this->set_backorders( 'no' );
+
+ // If we are stock managing and we don't have stock, force out of stock status.
+ } elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount' ) && 'no' === $this->get_backorders() ) {
+ $this->set_stock_status( 'outofstock' );
+
+ // If the stock level is changing and we do now have enough, force in stock status.
+ } elseif ( $this->get_stock_quantity() > get_option( 'woocommerce_notify_no_stock_amount' ) && array_key_exists( 'stock_quantity', $this->get_changes() ) ) {
+ $this->set_stock_status( 'instock' );
+ }
+ }
+
+ /**
+ * Save data (either create or update depending on if we are working on an existing product).
+ *
+ * @since 3.0.0
+ */
+ public function save() {
+ $this->validate_props();
+
+ if ( $this->data_store ) {
+ // Trigger action before saving to the DB. Use a pointer to adjust object props before save.
+ do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
+
+ if ( $this->get_id() ) {
+ $this->data_store->update( $this );
+ } else {
+ $this->data_store->create( $this );
+ }
+ if ( $this->get_parent_id() ) {
+ wc_deferred_product_sync( $this->get_parent_id() );
+ }
+ return $this->get_id();
+ }
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | Conditionals
+ |--------------------------------------------------------------------------
+ */
+
/**
* Check if a product supports a given feature.
*
@@ -167,186 +1319,12 @@ class WC_Product {
}
/**
- * Return the product ID
+ * Returns whether or not the product post exists.
*
- * @since 2.5.0
- * @return int product (post) ID
+ * @return bool
*/
- public function get_id() {
-
- return $this->id;
- }
-
- /**
- * get_gallery_attachment_ids function.
- *
- * @return array
- */
- public function get_gallery_attachment_ids() {
- return apply_filters( 'woocommerce_product_gallery_attachment_ids', array_filter( array_filter( (array) explode( ',', $this->product_image_gallery ) ), 'wp_attachment_is_image' ), $this );
- }
-
- /**
- * Wrapper for get_permalink.
- *
- * @return string
- */
- public function get_permalink() {
- return get_permalink( $this->id );
- }
-
- /**
- * Get SKU (Stock-keeping unit) - product unique ID.
- *
- * @return string
- */
- public function get_sku() {
- return apply_filters( 'woocommerce_get_sku', $this->sku, $this );
- }
-
- /**
- * Returns number of items available for sale.
- *
- * @return int
- */
- public function get_stock_quantity() {
- return apply_filters( 'woocommerce_get_stock_quantity', $this->managing_stock() ? wc_stock_amount( $this->stock ) : null, $this );
- }
-
- /**
- * Get total stock.
- *
- * This is the stock of parent and children combined.
- *
- * @return int
- */
- public function get_total_stock() {
- if ( empty( $this->total_stock ) ) {
- $this->total_stock = max( 0, $this->get_stock_quantity() );
-
- if ( sizeof( $this->get_children() ) > 0 ) {
- foreach ( $this->get_children() as $child_id ) {
- if ( 'yes' === get_post_meta( $child_id, '_manage_stock', true ) ) {
- $stock = get_post_meta( $child_id, '_stock', true );
- $this->total_stock += max( 0, wc_stock_amount( $stock ) );
- }
- }
- }
- }
- return wc_stock_amount( $this->total_stock );
- }
-
- /**
- * Check if the stock status needs changing.
- */
- public function check_stock_status() {
- if ( ! $this->backorders_allowed() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
- if ( $this->stock_status !== 'outofstock' ) {
- $this->set_stock_status( 'outofstock' );
- }
- } elseif ( $this->backorders_allowed() || $this->get_total_stock() > get_option( 'woocommerce_notify_no_stock_amount' ) ) {
- if ( $this->stock_status !== 'instock' ) {
- $this->set_stock_status( 'instock' );
- }
- }
- }
-
- /**
- * Set stock level of the product.
- *
- * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues).
- * We cannot rely on the original loaded value in case another order was made since then.
- *
- * @param int $amount (default: null)
- * @param string $mode can be set, add, or subtract
- * @return int new stock level
- */
- public function set_stock( $amount = null, $mode = 'set' ) {
- global $wpdb;
-
- if ( ! is_null( $amount ) && $this->managing_stock() ) {
-
- // Ensure key exists
- add_post_meta( $this->id, '_stock', 0, true );
-
- // Update stock in DB directly
- switch ( $mode ) {
- case 'add' :
- $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) );
- break;
- case 'subtract' :
- $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) );
- break;
- default :
- $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) );
- break;
- }
-
- // Clear caches
- wp_cache_delete( $this->id, 'post_meta' );
- delete_transient( 'wc_low_stock_count' );
- delete_transient( 'wc_outofstock_count' );
- unset( $this->stock );
-
- // Stock status
- $this->check_stock_status();
-
- // Trigger action
- do_action( 'woocommerce_product_set_stock', $this );
- }
-
- return $this->get_stock_quantity();
- }
-
- /**
- * Reduce stock level of the product.
- *
- * @param int $amount Amount to reduce by. Default: 1
- * @return int new stock level
- */
- public function reduce_stock( $amount = 1 ) {
- return $this->set_stock( $amount, 'subtract' );
- }
-
- /**
- * Increase stock level of the product.
- *
- * @param int $amount Amount to increase by. Default 1.
- * @return int new stock level
- */
- public function increase_stock( $amount = 1 ) {
- return $this->set_stock( $amount, 'add' );
- }
-
- /**
- * set_stock_status function.
- *
- * @param string $status
- */
- public function set_stock_status( $status ) {
-
- $status = ( 'outofstock' === $status ) ? 'outofstock' : 'instock';
-
- // Sanity check
- if ( $this->managing_stock() ) {
- if ( ! $this->backorders_allowed() && $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
- $status = 'outofstock';
- }
- }
-
- if ( update_post_meta( $this->id, '_stock_status', $status ) ) {
- $this->stock_status = $status;
- do_action( 'woocommerce_product_set_stock_status', $this->id, $status );
- }
- }
-
- /**
- * Return the product type.
- *
- * @return string
- */
- public function get_type() {
- return is_null( $this->product_type ) ? '' : $this->product_type;
+ public function exists() {
+ return false !== $this->get_status();
}
/**
@@ -358,7 +1336,7 @@ class WC_Product {
* @return bool
*/
public function is_type( $type ) {
- return ( $this->product_type == $type || ( is_array( $type ) && in_array( $this->product_type, $type ) ) ) ? true : false;
+ return ( $this->get_type() === $type || ( is_array( $type ) && in_array( $this->get_type(), $type ) ) );
}
/**
@@ -367,7 +1345,232 @@ class WC_Product {
* @return bool
*/
public function is_downloadable() {
- return $this->downloadable == 'yes' ? true : false;
+ return apply_filters( 'woocommerce_is_downloadable', true === $this->get_downloadable(), $this );
+ }
+
+ /**
+ * Checks if a product is virtual (has no shipping).
+ *
+ * @return bool
+ */
+ public function is_virtual() {
+ return apply_filters( 'woocommerce_is_virtual', true === $this->get_virtual(), $this );
+ }
+
+ /**
+ * Returns whether or not the product is featured.
+ *
+ * @return bool
+ */
+ public function is_featured() {
+ return true === $this->get_featured();
+ }
+
+ /**
+ * Check if a product is sold individually (no quantities).
+ *
+ * @return bool
+ */
+ public function is_sold_individually() {
+ return apply_filters( 'woocommerce_is_sold_individually', true === $this->get_sold_individually(), $this );
+ }
+
+ /**
+ * Returns whether or not the product is visible in the catalog.
+ *
+ * @return bool
+ */
+ public function is_visible() {
+ $visible = 'visible' === $this->get_catalog_visibility() || ( is_search() && 'search' === $this->get_catalog_visibility() ) || ( ! is_search() && 'catalog' === $this->get_catalog_visibility() );
+
+ if ( 'publish' !== $this->get_status() && ! current_user_can( 'edit_post', $this->get_id() ) ) {
+ $visible = false;
+ }
+
+ if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->is_in_stock() ) {
+ $visible = false;
+ }
+
+ return apply_filters( 'woocommerce_product_is_visible', $visible, $this->get_id() );
+ }
+
+ /**
+ * Returns false if the product cannot be bought.
+ *
+ * @return bool
+ */
+ public function is_purchasable() {
+ return apply_filters( 'woocommerce_is_purchasable', $this->exists() && ( 'publish' === $this->get_status() || current_user_can( 'edit_post', $this->get_id() ) ) && '' !== $this->get_price(), $this );
+ }
+
+ /**
+ * Returns whether or not the product is on sale.
+ *
+ * @param string $context What the value is for. Valid values are view and edit.
+ * @return bool
+ */
+ public function is_on_sale( $context = 'view' ) {
+ if ( '' !== (string) $this->get_sale_price( $context ) && $this->get_regular_price( $context ) > $this->get_sale_price( $context ) ) {
+ $on_sale = true;
+
+ if ( $this->get_date_on_sale_from( $context ) && $this->get_date_on_sale_from( $context )->getTimestamp() > current_time( 'timestamp', true ) ) {
+ $on_sale = false;
+ }
+
+ if ( $this->get_date_on_sale_to( $context ) && $this->get_date_on_sale_to( $context )->getTimestamp() < current_time( 'timestamp', true ) ) {
+ $on_sale = false;
+ }
+ } else {
+ $on_sale = false;
+ }
+ return 'view' === $context ? apply_filters( 'woocommerce_product_is_on_sale', $on_sale, $this ) : $on_sale;
+ }
+
+ /**
+ * Returns whether or not the product has dimensions set.
+ *
+ * @return bool
+ */
+ public function has_dimensions() {
+ return ( $this->get_length() || $this->get_height() || $this->get_width() ) && ! $this->get_virtual();
+ }
+
+ /**
+ * Returns whether or not the product has weight set.
+ *
+ * @return bool
+ */
+ public function has_weight() {
+ return $this->get_weight() && ! $this->get_virtual();
+ }
+
+ /**
+ * Returns whether or not the product is in stock.
+ *
+ * @return bool
+ */
+ public function is_in_stock() {
+ return apply_filters( 'woocommerce_product_is_in_stock', 'instock' === $this->get_stock_status(), $this );
+ }
+
+ /**
+ * Checks if a product needs shipping.
+ *
+ * @return bool
+ */
+ public function needs_shipping() {
+ return apply_filters( 'woocommerce_product_needs_shipping', ! $this->is_virtual(), $this );
+ }
+
+ /**
+ * Returns whether or not the product is taxable.
+ *
+ * @return bool
+ */
+ public function is_taxable() {
+ return apply_filters( 'woocommerce_product_is_taxable', $this->get_tax_status() === 'taxable' && wc_tax_enabled(), $this );
+ }
+
+ /**
+ * Returns whether or not the product shipping is taxable.
+ *
+ * @return bool
+ */
+ public function is_shipping_taxable() {
+ return $this->get_tax_status() === 'taxable' || $this->get_tax_status() === 'shipping';
+ }
+
+ /**
+ * Returns whether or not the product is stock managed.
+ *
+ * @return bool
+ */
+ public function managing_stock() {
+ if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
+ return $this->get_manage_stock();
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether or not the product can be backordered.
+ *
+ * @return bool
+ */
+ public function backorders_allowed() {
+ return apply_filters( 'woocommerce_product_backorders_allowed', ( 'yes' === $this->get_backorders() || 'notify' === $this->get_backorders() ), $this->get_id(), $this );
+ }
+
+ /**
+ * Returns whether or not the product needs to notify the customer on backorder.
+ *
+ * @return bool
+ */
+ public function backorders_require_notification() {
+ return apply_filters( 'woocommerce_product_backorders_require_notification', ( $this->managing_stock() && 'notify' === $this->get_backorders() ), $this );
+ }
+
+ /**
+ * Check if a product is on backorder.
+ *
+ * @param int $qty_in_cart (default: 0)
+ * @return bool
+ */
+ public function is_on_backorder( $qty_in_cart = 0 ) {
+ return $this->managing_stock() && $this->backorders_allowed() && ( $this->get_stock_quantity() - $qty_in_cart ) < 0 ? true : false;
+ }
+
+ /**
+ * Returns whether or not the product has enough stock for the order.
+ *
+ * @param mixed $quantity
+ * @return bool
+ */
+ public function has_enough_stock( $quantity ) {
+ return ! $this->managing_stock() || $this->backorders_allowed() || $this->get_stock_quantity() >= $quantity;
+ }
+
+ /**
+ * Returns whether or not the product has any visible attributes.
+ *
+ * @return boolean
+ */
+ public function has_attributes() {
+ foreach ( $this->get_attributes() as $attribute ) {
+ if ( $attribute->get_visible() ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether or not the product has any child product.
+ *
+ * @return bool
+ */
+ public function has_child() {
+ return 0 < count( $this->get_children() );
+ }
+
+ /**
+ * Does a child have dimensions?
+ *
+ * @since 3.0.0
+ * @return bool
+ */
+ public function child_has_dimensions() {
+ return false;
+ }
+
+ /**
+ * Does a child have a weight?
+ *
+ * @since 3.0.0
+ * @return boolean
+ */
+ public function child_has_weight() {
+ return false;
}
/**
@@ -379,191 +1582,109 @@ class WC_Product {
* @return bool Whether downloadable product has a file attached.
*/
public function has_file( $download_id = '' ) {
- return ( $this->is_downloadable() && $this->get_file( $download_id ) ) ? true : false;
+ return $this->is_downloadable() && $this->get_file( $download_id );
}
/**
- * Gets an array of downloadable files for this product.
+ * Returns whether or not the product has additonal options that need
+ * selecting before adding to cart.
*
- * @since 2.1.0
- *
- * @return array
+ * @since 3.0.0
+ * @return boolean
*/
- public function get_files() {
-
- $downloadable_files = array_filter( isset( $this->downloadable_files ) ? (array) maybe_unserialize( $this->downloadable_files ) : array() );
-
- if ( ! empty( $downloadable_files ) ) {
-
- foreach ( $downloadable_files as $key => $file ) {
-
- if ( ! is_array( $file ) ) {
- $downloadable_files[ $key ] = array(
- 'file' => $file,
- 'name' => ''
- );
- }
-
- // Set default name
- if ( empty( $file['name'] ) ) {
- $downloadable_files[ $key ]['name'] = wc_get_filename_from_url( $file['file'] );
- }
-
- // Filter URL
- $downloadable_files[ $key ]['file'] = apply_filters( 'woocommerce_file_download_path', $downloadable_files[ $key ]['file'], $this, $key );
- }
- }
-
- return apply_filters( 'woocommerce_product_files', $downloadable_files, $this );
+ public function has_options() {
+ return false;
}
- /**
- * Get a file by $download_id.
- *
- * @param string $download_id file identifier
- * @return array|false if not found
- */
- public function get_file( $download_id = '' ) {
-
- $files = $this->get_files();
-
- if ( '' === $download_id ) {
- $file = sizeof( $files ) ? current( $files ) : false;
- } elseif ( isset( $files[ $download_id ] ) ) {
- $file = $files[ $download_id ];
- } else {
- $file = false;
- }
-
- // allow overriding based on the particular file being requested
- return apply_filters( 'woocommerce_product_file', $file, $this, $download_id );
- }
+ /*
+ |--------------------------------------------------------------------------
+ | Non-CRUD Getters
+ |--------------------------------------------------------------------------
+ */
/**
- * Get file download path identified by $download_id.
+ * Get the product's title. For products this is the product name.
*
- * @param string $download_id file identifier
* @return string
*/
- public function get_file_download_path( $download_id ) {
- $files = $this->get_files();
-
- if ( isset( $files[ $download_id ] ) ) {
- $file_path = $files[ $download_id ]['file'];
- } else {
- $file_path = '';
- }
-
- // allow overriding based on the particular file being requested
- return apply_filters( 'woocommerce_product_file_download_path', $file_path, $this, $download_id );
+ public function get_title() {
+ return apply_filters( 'woocommerce_product_title', $this->get_name(), $this );
}
/**
- * Checks if a product is virtual (has no shipping).
- *
- * @return bool
+ * Product permalink.
+ * @return string
*/
- public function is_virtual() {
- return apply_filters( 'woocommerce_is_virtual', $this->virtual == 'yes' ? true : false, $this );
+ public function get_permalink() {
+ return get_permalink( $this->get_id() );
}
/**
- * Checks if a product needs shipping.
+ * Returns the children IDs if applicable. Overridden by child classes.
*
- * @return bool
- */
- public function needs_shipping() {
- return apply_filters( 'woocommerce_product_needs_shipping', $this->is_virtual() ? false : true, $this );
- }
-
- /**
- * Check if a product is sold individually (no quantities).
- *
- * @return bool
- */
- public function is_sold_individually() {
-
- $return = false;
-
- if ( 'yes' == $this->sold_individually ) {
- $return = true;
- }
-
- return apply_filters( 'woocommerce_is_sold_individually', $return, $this );
- }
-
- /**
- * get_child function.
- *
- * @param mixed $child_id
- * @return WC_Product WC_Product or WC_Product_variation
- */
- public function get_child( $child_id ) {
- return wc_get_product( $child_id );
- }
-
- /**
- * get_children function.
- *
- * @return array
+ * @return array of IDs
*/
public function get_children() {
return array();
}
/**
- * Returns whether or not the product has any child product.
- *
- * @return bool
- */
- public function has_child() {
- return false;
- }
-
- /**
- * Returns whether or not the product post exists.
- *
- * @return bool
- */
- public function exists() {
- return empty( $this->post ) ? false : true;
- }
-
- /**
- * Returns whether or not the product is taxable.
- *
- * @return bool
- */
- public function is_taxable() {
- $taxable = $this->get_tax_status() === 'taxable' && wc_tax_enabled() ? true : false;
- return apply_filters( 'woocommerce_product_is_taxable', $taxable, $this );
- }
-
- /**
- * Returns whether or not the product shipping is taxable.
- *
- * @return bool
- */
- public function is_shipping_taxable() {
- return $this->get_tax_status() === 'taxable' || $this->get_tax_status() === 'shipping' ? true : false;
- }
-
- /**
- * Get the title of the post.
- *
- * @return string
- */
- public function get_title() {
- return apply_filters( 'woocommerce_product_title', $this->post ? $this->post->post_title : '', $this );
- }
-
- /**
- * Get the parent of the post.
- *
+ * If the stock level comes from another product ID, this should be modified.
+ * @since 3.0.0
* @return int
*/
- public function get_parent() {
- return apply_filters( 'woocommerce_product_parent', absint( $this->post->post_parent ), $this );
+ public function get_stock_managed_by_id() {
+ return $this->get_id();
+ }
+
+ /**
+ * Returns the price in html format.
+ * @return string
+ */
+ public function get_price_html( $deprecated = '' ) {
+ if ( '' === $this->get_price() ) {
+ $price = apply_filters( 'woocommerce_empty_price_html', '', $this );
+ } elseif ( $this->is_on_sale() ) {
+ $price = wc_format_sale_price( wc_get_price_to_display( $this, array( 'price' => $this->get_regular_price() ) ), wc_get_price_to_display( $this ) ) . $this->get_price_suffix();
+ } else {
+ $price = wc_price( wc_get_price_to_display( $this ) ) . $this->get_price_suffix();
+ }
+
+ return apply_filters( 'woocommerce_get_price_html', $price, $this );
+ }
+
+ /**
+ * Get product name with SKU or ID. Used within admin.
+ *
+ * @return string Formatted product name
+ */
+ public function get_formatted_name() {
+ if ( $this->get_sku() ) {
+ $identifier = $this->get_sku();
+ } else {
+ $identifier = '#' . $this->get_id();
+ }
+ return sprintf( '%2$s (%1$s)', $identifier, $this->get_name() );
+ }
+
+ /**
+ * Get min quantity which can be purchased at once.
+ *
+ * @since 3.0.0
+ * @return int
+ */
+ public function get_min_purchase_quantity() {
+ return 1;
+ }
+
+ /**
+ * Get max quantity which can be purchased at once.
+ *
+ * @since 3.0.0
+ * @return int Quantity or -1 if unlimited.
+ */
+ public function get_max_purchase_quantity() {
+ return $this->is_sold_individually() ? 1 : ( $this->backorders_allowed() || ! $this->get_manage_stock() ? -1 : $this->get_stock_quantity() );
}
/**
@@ -572,7 +1693,7 @@ class WC_Product {
* @return string
*/
public function add_to_cart_url() {
- return apply_filters( 'woocommerce_product_add_to_cart_url', get_permalink( $this->id ), $this );
+ return apply_filters( 'woocommerce_product_add_to_cart_url', $this->get_permalink(), $this );
}
/**
@@ -594,353 +1715,110 @@ class WC_Product {
}
/**
- * Returns whether or not the product is stock managed.
+ * Returns the main product image.
*
- * @return bool
+ * @param string $size (default: 'shop_thumbnail')
+ * @param array $attr
+ * @param bool True to return $placeholder if no image is found, or false to return an empty string.
+ * @return string
*/
- public function managing_stock() {
- return ( ! isset( $this->manage_stock ) || $this->manage_stock == 'no' || get_option( 'woocommerce_manage_stock' ) !== 'yes' ) ? false : true;
- }
-
- /**
- * Returns whether or not the product is in stock.
- *
- * @return bool
- */
- public function is_in_stock() {
- if ( $this->managing_stock() && $this->backorders_allowed() ) {
- return true;
- } elseif ( $this->managing_stock() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
- return false;
+ public function get_image( $size = 'shop_thumbnail', $attr = array(), $placeholder = true ) {
+ if ( has_post_thumbnail( $this->get_id() ) ) {
+ $image = get_the_post_thumbnail( $this->get_id(), $size, $attr );
+ } elseif ( ( $parent_id = wp_get_post_parent_id( $this->get_id() ) ) && has_post_thumbnail( $parent_id ) ) {
+ $image = get_the_post_thumbnail( $parent_id, $size, $attr );
+ } elseif ( $placeholder ) {
+ $image = wc_placeholder_img( $size );
} else {
- return $this->stock_status === 'instock';
+ $image = '';
}
+ return str_replace( array( 'https://', 'http://' ), '//', $image );
}
/**
- * Returns whether or not the product can be backordered.
- *
- * @return bool
- */
- public function backorders_allowed() {
- return apply_filters( 'woocommerce_product_backorders_allowed', $this->backorders === 'yes' || $this->backorders === 'notify' ? true : false, $this->id );
- }
-
- /**
- * Returns whether or not the product needs to notify the customer on backorder.
- *
- * @return bool
- */
- public function backorders_require_notification() {
- return $this->managing_stock() && $this->backorders === 'notify' ? true : false;
- }
-
- /**
- * Check if a product is on backorder.
- *
- * @param int $qty_in_cart (default: 0)
- * @return bool
- */
- public function is_on_backorder( $qty_in_cart = 0 ) {
- return $this->managing_stock() && $this->backorders_allowed() && ( $this->get_total_stock() - $qty_in_cart ) < 0 ? true : false;
- }
-
- /**
- * Returns whether or not the product has enough stock for the order.
- *
- * @param mixed $quantity
- * @return bool
- */
- public function has_enough_stock( $quantity ) {
- return ! $this->managing_stock() || $this->backorders_allowed() || $this->get_stock_quantity() >= $quantity ? true : false;
- }
-
- /**
- * Returns the availability of the product.
+ * Returns the product shipping class SLUG.
*
* @return string
*/
- public function get_availability() {
- $availability = $class = '';
+ public function get_shipping_class() {
+ if ( $class_id = $this->get_shipping_class_id() ) {
+ $term = get_term_by( 'id', $class_id, 'product_shipping_class' );
- if ( $this->managing_stock() ) {
-
- if ( $this->is_in_stock() && $this->get_total_stock() > get_option( 'woocommerce_notify_no_stock_amount' ) ) {
-
- switch ( get_option( 'woocommerce_stock_format' ) ) {
-
- case 'no_amount' :
- $availability = __( 'In stock', 'woocommerce' );
- break;
-
- case 'low_amount' :
- if ( $this->get_total_stock() <= get_option( 'woocommerce_notify_low_stock_amount' ) ) {
- $availability = sprintf( __( 'Only %s left in stock', 'woocommerce' ), $this->get_total_stock() );
-
- if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
- $availability .= ' ' . __( '(can be backordered)', 'woocommerce' );
- }
- } else {
- $availability = __( 'In stock', 'woocommerce' );
- }
- break;
-
- default :
- $availability = sprintf( __( '%s in stock', 'woocommerce' ), $this->get_total_stock() );
-
- if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
- $availability .= ' ' . __( '(can be backordered)', 'woocommerce' );
- }
- break;
- }
-
- $class = 'in-stock';
-
- } elseif ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
-
- $availability = __( 'Available on backorder', 'woocommerce' );
- $class = 'available-on-backorder';
-
- } elseif ( $this->backorders_allowed() ) {
-
- $availability = __( 'In stock', 'woocommerce' );
- $class = 'in-stock';
-
- } else {
-
- $availability = __( 'Out of stock', 'woocommerce' );
- $class = 'out-of-stock';
+ if ( $term && ! is_wp_error( $term ) ) {
+ return $term->slug;
}
-
- } elseif ( ! $this->is_in_stock() ) {
-
- $availability = __( 'Out of stock', 'woocommerce' );
- $class = 'out-of-stock';
}
-
- return apply_filters( 'woocommerce_get_availability', array( 'availability' => $availability, 'class' => $class ), $this );
+ return '';
}
/**
- * Returns whether or not the product is featured.
- *
- * @return bool
- */
- public function is_featured() {
- return $this->featured === 'yes' ? true : false;
- }
-
- /**
- * Returns whether or not the product is visible in the catalog.
- *
- * @return bool
- */
- public function is_visible() {
- if ( ! $this->post ) {
- $visible = false;
-
- // Published/private
- } elseif ( $this->post->post_status !== 'publish' && ! current_user_can( 'edit_post', $this->id ) ) {
- $visible = false;
-
- // Out of stock visibility
- } elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->is_in_stock() ) {
- $visible = false;
-
- // visibility setting
- } elseif ( 'hidden' === $this->visibility ) {
- $visible = false;
- } elseif ( 'visible' === $this->visibility ) {
- $visible = true;
-
- // Visibility in loop
- } elseif ( is_search() ) {
- $visible = 'search' === $this->visibility;
- } else {
- $visible = 'catalog' === $this->visibility;
- }
-
- return apply_filters( 'woocommerce_product_is_visible', $visible, $this->id );
- }
-
- /**
- * Returns whether or not the product is on sale.
- *
- * @return bool
- */
- public function is_on_sale() {
- return apply_filters( 'woocommerce_product_is_on_sale', ( $this->get_sale_price() !== $this->get_regular_price() && $this->get_sale_price() === $this->get_price() ), $this );
- }
-
- /**
- * Returns false if the product cannot be bought.
- *
- * @return bool
- */
- public function is_purchasable() {
-
- $purchasable = true;
-
- // Products must exist of course
- if ( ! $this->exists() ) {
- $purchasable = false;
-
- // Other products types need a price to be set
- } elseif ( $this->get_price() === '' ) {
- $purchasable = false;
-
- // Check the product is published
- } elseif ( $this->post->post_status !== 'publish' && ! current_user_can( 'edit_post', $this->id ) ) {
- $purchasable = false;
- }
-
- return apply_filters( 'woocommerce_is_purchasable', $purchasable, $this );
- }
-
- /**
- * Set a products price dynamically.
- *
- * @param float $price Price to set.
- */
- public function set_price( $price ) {
- $this->price = $price;
- }
-
- /**
- * Adjust a products price dynamically.
- *
- * @param mixed $price
- */
- public function adjust_price( $price ) {
- $this->price = $this->price + $price;
- }
-
- /**
- * Returns the product's sale price.
- *
- * @return string price
- */
- public function get_sale_price() {
- return apply_filters( 'woocommerce_get_sale_price', $this->sale_price, $this );
- }
-
- /**
- * Returns the product's regular price.
- *
- * @return string price
- */
- public function get_regular_price() {
- return apply_filters( 'woocommerce_get_regular_price', $this->regular_price, $this );
- }
-
- /**
- * Returns the product's active price.
- *
- * @return string price
- */
- public function get_price() {
- return apply_filters( 'woocommerce_get_price', $this->price, $this );
- }
-
- /**
- * Returns the price (including tax). Uses customer tax rates. Can work for a specific $qty for more accurate taxes.
- *
- * @param string $price to calculate, left blank to just use get_price()
+ * Returns a single product attribute as a string.
+ * @param string $attribute to get.
* @return string
*/
- public function get_price_including_tax( $qty = 1, $price = '' ) {
-
- if ( $price === '' ) {
- $price = $this->get_price();
- }
-
- if ( $this->is_taxable() ) {
-
- if ( get_option( 'woocommerce_prices_include_tax' ) === 'no' ) {
-
- $tax_rates = WC_Tax::get_rates( $this->get_tax_class() );
- $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, false );
- $tax_amount = WC_Tax::get_tax_total( $taxes );
- $price = round( $price * $qty + $tax_amount, wc_get_price_decimals() );
-
- } else {
-
- $tax_rates = WC_Tax::get_rates( $this->get_tax_class() );
- $base_tax_rates = WC_Tax::get_base_tax_rates( $this->tax_class );
-
- if ( ! empty( WC()->customer ) && WC()->customer->is_vat_exempt() ) {
-
- $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true );
- $base_tax_amount = array_sum( $base_taxes );
- $price = round( $price * $qty - $base_tax_amount, wc_get_price_decimals() );
-
- /**
- * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
- * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
- * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
- */
- } elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
-
- $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true );
- $modded_taxes = WC_Tax::calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false );
- $price = round( ( $price * $qty ) - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() );
-
- } else {
-
- $price = $price * $qty;
-
- }
-
- }
+ public function get_attribute( $attribute ) {
+ $attributes = $this->get_attributes();
+ $attribute = sanitize_title( $attribute );
+ if ( isset( $attributes[ $attribute ] ) ) {
+ $attribute_object = $attributes[ $attribute ];
+ } elseif ( isset( $attributes[ 'pa_' . $attribute ] ) ) {
+ $attribute_object = $attributes[ 'pa_' . $attribute ];
} else {
- $price = $price * $qty;
+ return '';
}
-
- return apply_filters( 'woocommerce_get_price_including_tax', $price, $qty, $this );
+ return $attribute_object->is_taxonomy() ? implode( ', ', wc_get_product_terms( $this->get_id(), $attribute_object->get_name(), array( 'fields' => 'names' ) ) ) : wc_implode_text_attributes( $attribute_object->get_options() );
}
/**
- * Returns the price (excluding tax) - ignores tax_class filters since the price may *include* tax and thus needs subtracting.
- * Uses store base tax rates. Can work for a specific $qty for more accurate taxes.
- *
- * @param string $price to calculate, left blank to just use get_price()
- * @return string
+ * Get the total amount (COUNT) of ratings, or just the count for one rating e.g. number of 5 star ratings.
+ * @param int $value Optional. Rating value to get the count for. By default returns the count of all rating values.
+ * @return int
*/
- public function get_price_excluding_tax( $qty = 1, $price = '' ) {
+ public function get_rating_count( $value = null ) {
+ $counts = $this->get_rating_counts();
- if ( $price === '' ) {
- $price = $this->get_price();
- }
-
- if ( $this->is_taxable() && 'yes' === get_option( 'woocommerce_prices_include_tax' ) ) {
- $tax_rates = WC_Tax::get_base_tax_rates( $this->tax_class );
- $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true );
- $price = WC_Tax::round( $price * $qty - array_sum( $taxes ) );
+ if ( is_null( $value ) ) {
+ return array_sum( $counts );
+ } elseif ( isset( $counts[ $value ] ) ) {
+ return absint( $counts[ $value ] );
} else {
- $price = $price * $qty;
+ return 0;
}
-
- return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $this );
}
/**
- * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting.
+ * Get a file by $download_id.
*
- * @param string $price to calculate, left blank to just use get_price()
- * @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax()
- * @return string
+ * @param string $download_id file identifier
+ * @return array|false if not found
*/
- public function get_display_price( $price = '', $qty = 1 ) {
+ public function get_file( $download_id = '' ) {
+ $files = $this->get_downloads();
- if ( $price === '' ) {
- $price = $this->get_price();
+ if ( '' === $download_id ) {
+ $file = sizeof( $files ) ? current( $files ) : false;
+ } elseif ( isset( $files[ $download_id ] ) ) {
+ $file = $files[ $download_id ];
+ } else {
+ $file = false;
}
- $tax_display_mode = get_option( 'woocommerce_tax_display_shop' );
- $display_price = $tax_display_mode == 'incl' ? $this->get_price_including_tax( $qty, $price ) : $this->get_price_excluding_tax( $qty, $price );
+ return apply_filters( 'woocommerce_product_file', $file, $this, $download_id );
+ }
- return $display_price;
+ /**
+ * Get file download path identified by $download_id.
+ *
+ * @param string $download_id file identifier
+ * @return string
+ */
+ public function get_file_download_path( $download_id ) {
+ $files = $this->get_downloads();
+ $file_path = isset( $files[ $download_id ] ) ? $files[ $download_id ]->get_file() : '';
+
+ // allow overriding based on the particular file being requested
+ return apply_filters( 'woocommerce_product_file_download_path', $file_path, $this, $download_id );
}
/**
@@ -951,658 +1829,64 @@ class WC_Product {
* @return string
*/
public function get_price_suffix( $price = '', $qty = 1 ) {
+ $html = '';
- if ( $price === '' ) {
- $price = $this->get_price();
- }
-
- $price_display_suffix = get_option( 'woocommerce_price_display_suffix' );
-
- if ( $price_display_suffix ) {
-
- $price_display_suffix = ' ' . $price_display_suffix . '';
-
- $find = array(
- '{price_including_tax}',
- '{price_excluding_tax}'
+ if ( ( $suffix = get_option( 'woocommerce_price_display_suffix' ) ) && wc_tax_enabled() && 'taxable' === $this->get_tax_status() ) {
+ if ( '' === $price ) {
+ $price = $this->get_price();
+ }
+ $replacements = array(
+ '{price_including_tax}' => wc_price( wc_get_price_including_tax( $this, array( 'qty' => $qty, 'price' => $price ) ) ),
+ '{price_excluding_tax}' => wc_price( wc_get_price_excluding_tax( $this, array( 'qty' => $qty, 'price' => $price ) ) ),
);
-
- $replace = array(
- wc_price( $this->get_price_including_tax( $qty, $price ) ),
- wc_price( $this->get_price_excluding_tax( $qty, $price ) )
- );
-
- $price_display_suffix = str_replace( $find, $replace, $price_display_suffix );
+ $html = str_replace( array_keys( $replacements ), array_values( $replacements ), ' ' . wp_kses_post( $suffix ) . '' );
}
-
- return apply_filters( 'woocommerce_get_price_suffix', $price_display_suffix, $this );
+ return apply_filters( 'woocommerce_get_price_suffix', $html, $this, $price, $qty );
}
/**
- * Returns the price in html format.
+ * Returns the availability of the product.
*
- * @param string $price (default: '')
- * @return string
+ * @return string[]
*/
- public function get_price_html( $price = '' ) {
-
- $display_price = $this->get_display_price();
- $display_regular_price = $this->get_display_price( $this->get_regular_price() );
-
- if ( $this->get_price() > 0 ) {
-
- if ( $this->is_on_sale() && $this->get_regular_price() ) {
-
- $price .= $this->get_price_html_from_to( $display_regular_price, $display_price ) . $this->get_price_suffix();
-
- $price = apply_filters( 'woocommerce_sale_price_html', $price, $this );
-
- } else {
-
- $price .= wc_price( $display_price ) . $this->get_price_suffix();
-
- $price = apply_filters( 'woocommerce_price_html', $price, $this );
-
- }
-
- } elseif ( $this->get_price() === '' ) {
-
- $price = apply_filters( 'woocommerce_empty_price_html', '', $this );
-
- } elseif ( $this->get_price() == 0 ) {
-
- if ( $this->is_on_sale() && $this->get_regular_price() ) {
-
- $price .= $this->get_price_html_from_to( $display_regular_price, __( 'Free!', 'woocommerce' ) );
-
- $price = apply_filters( 'woocommerce_free_sale_price_html', $price, $this );
-
- } else {
-
- $price = '' . __( 'Free!', 'woocommerce' ) . '';
-
- $price = apply_filters( 'woocommerce_free_price_html', $price, $this );
-
- }
- }
-
- return apply_filters( 'woocommerce_get_price_html', $price, $this );
+ public function get_availability() {
+ return apply_filters( 'woocommerce_get_availability', array(
+ 'availability' => $this->get_availability_text(),
+ 'class' => $this->get_availability_class(),
+ ), $this );
}
/**
- * Functions for getting parts of a price, in html, used by get_price_html.
+ * Get availability text based on stock status.
*
* @return string
*/
- public function get_price_html_from_text() {
- $from = '' . _x( 'From:', 'min_price', 'woocommerce' ) . ' ';
-
- return apply_filters( 'woocommerce_get_price_html_from_text', $from, $this );
- }
-
- /**
- * Functions for getting parts of a price, in html, used by get_price_html.
- *
- * @param string $from String or float to wrap with 'from' text
- * @param mixed $to String or float to wrap with 'to' text
- * @return string
- */
- public function get_price_html_from_to( $from, $to ) {
- $price = '' . wp_kses_post( $error ) . '
'; + } + echo '