Merge branch 'trunk' into update/extensions-search-all-categories

This commit is contained in:
Bero 2021-04-28 11:06:24 +02:00
commit 1d232ddd1f
73 changed files with 2025 additions and 515 deletions

View File

@ -0,0 +1,30 @@
name: Build release zip file
on:
workflow_dispatch:
inputs:
ref:
description: 'By default the zip file is generated from the branch the workflow runs from, but you can specify an explicit reference to use instead here (e.g. refs/tags/tag_name). The resulting file will be available as an artifact on the workflow run.'
required: false
default: ''
jobs:
build:
name: Build release zip file
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Build the zip file
id: build
uses: woocommerce/action-build@v2
- name: Unzip the file (prevents double zip problem)
run: unzip ${{ steps.build.outputs.zip_path }} -d zipfile
- name: Upload the zip file as an artifact
uses: actions/upload-artifact@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: woocommerce
path: zipfile
retention-days: 7

View File

@ -0,0 +1,32 @@
name: "Pull request post-merge processing"
on:
pull_request:
types: [closed]
jobs:
assign-milestone:
name: "Assign milestone to merged pull request"
if: github.event.pull_request.merged == true && ! github.event.pull_request.milestone
runs-on: ubuntu-latest
steps:
- name: "Get the milestone changer script"
run: |
curl \
--silent \
--fail \
--header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \
--header 'User-Agent: GitHub action to set the milestone for a pull request' \
--header 'Accept: application/vnd.github.v3.raw' \
--remote-name \
--location $GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/assign-milestone-to-merged-pr.php
env:
GITHUB_API_URL: ${{ env.GITHUB_API_URL }}
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
- name: "Run the milestone changer script"
run: php assign-milestone-to-merged-pr.php
env:
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,144 @@
<?php
/**
* Script to automatically assign a milestone to a pull request when it's merged.
*
* @package WooCommerce/GithubActions
*/
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions
global $repo_owner, $repo_name, $github_token, $graphql_api_url;
/**
* Grab/process input.
*/
$repo_parts = explode( '/', getenv( 'GITHUB_REPOSITORY' ) );
$repo_owner = $repo_parts[0];
$repo_name = $repo_parts[1];
$pr_id = getenv( 'PULL_REQUEST_ID' );
$github_token = getenv( 'GITHUB_TOKEN' );
$graphql_api_url = getenv( 'GITHUB_GRAPHQL_URL' );
/**
* Select the milestone to be added:
*
* 1. Get the first 10 milestones sorted by creation date descending.
* (we'll never have more than 2 or 3 active milestones but let's get 10 to be sure).
* 2. Discard those not open or whose title is not a proper version number ("X.Y.Z").
* 3. Sort descending using version_compare.
* 4. Get the oldest one that does not have a corresponding "release/X.Y" branch.
*/
echo "Getting the list of milestones...\n";
$query = "
repository(owner:\"$repo_owner\", name:\"$repo_name\") {
milestones(first: 10, states: [OPEN], orderBy: {field: CREATED_AT, direction: DESC}) {
nodes {
id
title
state
}
}
}
";
$json = do_graphql_api_request( $query );
$milestones = $json['data']['repository']['milestones']['nodes'];
$milestones = array_map(
function( $x ) {
return 1 === preg_match( '/^\d+\.\d+\.\d+$/D', $x['title'] ) ? $x : null;
},
$milestones
);
$milestones = array_filter( $milestones );
usort(
$milestones,
function( $a, $b ) {
return version_compare( $b['title'], $a['title'] );
}
);
echo 'Latest open milestone: ' . $milestones[0]['title'] . "\n";
$chosen_milestone = null;
foreach ( $milestones as $milestone ) {
$milestone_title_parts = explode( '.', $milestone['title'] );
$milestone_release_branch = 'release/' . $milestone_title_parts[0] . '.' . $milestone_title_parts[1];
$query = "
repository(owner:\"$repo_owner\", name:\"$repo_name\") {
ref(qualifiedName: \"refs/heads/$milestone_release_branch\") {
id
}
}
";
$result = do_graphql_api_request( $query );
if ( is_null( $result['data']['repository']['ref'] ) ) {
$chosen_milestone = $milestone;
} else {
break;
}
}
// If all the milestones have a release branch, just take the newest one.
if ( is_null( $chosen_milestone ) ) {
$chosen_milestone = $milestones[0];
}
echo 'Milestone that will be assigned: ' . $chosen_milestone['title'] . "\n";
if ( getenv( 'DRY_RUN' ) ) {
echo "Dry run, skipping the actual milestone assignment\n";
return;
}
/*
* Assign the milestone to the pull request.
*/
echo "Assigning the milestone to the pull request...\n";
$milestone_id = $chosen_milestone['id'];
$mutation = "
updatePullRequest(input: {pullRequestId: \"$pr_id\", milestoneId: \"$milestone_id\"}) {
clientMutationId
}
";
do_graphql_api_request( $mutation, true );
/**
* Function to query the GitHub GraphQL API.
*
* @param string $body The GraphQL-formatted request body, without "query" or "mutation" wrapper.
* @param bool $is_mutation True if the request is a mutation, false if it's a query.
* @return array The json-decoded response.
*/
function do_graphql_api_request( $body, $is_mutation = false ) {
global $github_token, $graphql_api_url;
$keyword = $is_mutation ? 'mutation' : 'query';
$data = array( 'query' => "$keyword { $body }" );
$context = stream_context_create(
array(
'http' => array(
'method' => 'POST',
'header' => array(
'Accept: application/json',
'Content-Type: application/json',
'User-Agent: GitHub action to set the milestone for a pull request',
'Authorization: bearer ' . $github_token,
),
'content' => json_encode( $data ),
),
)
);
$result = file_get_contents( $graphql_api_url, false, $context );
return json_decode( $result, true );
}
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions

View File

@ -55,8 +55,11 @@ jQuery( function( $ ) {
var wc_country_select_select2 = function() {
$( 'select.country_select:visible, select.state_select:visible' ).each( function() {
var $this = $( this );
var select2_args = $.extend({
placeholder: $( this ).attr( 'data-placeholder' ) || $( this ).attr( 'placeholder' ) || '',
placeholder: $this.attr( 'data-placeholder' ) || $this.attr( 'placeholder' ) || '',
label: $this.attr( 'data-label' ) || null,
width: '100%'
}, getEnhancedSelectFormatString() );

View File

@ -1,27 +1,41 @@
/*!
* Select2 4.0.3
* https://select2.github.io
* SelectWoo 1.0.9
* https://github.com/woocommerce/selectWoo
*
* Released under the MIT license
* https://github.com/select2/select2/blob/master/LICENSE.md
* https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
factory(require('jquery'));
module.exports = function (root, jQuery) {
if (jQuery === undefined) {
// require('jQuery') returns a factory that requires window to
// build a jQuery instance, we normalize how we use modules
// that require this pattern but the window provided is a noop
// if it's defined (how jquery works)
if (typeof window !== 'undefined') {
jQuery = require('jquery');
}
else {
jQuery = require('jquery')(root);
}
}
factory(jQuery);
return jQuery;
};
} else {
// Browser globals
factory(jQuery);
}
}(function (jQuery) {
} (function (jQuery) {
// This is needed so we can catch the AMD loader configuration and use it
// The inner file should be wrapped (by `banner.start.js`) in a function that
// returns the AMD loader references.
var S2 =
(function () {
var S2 =(function () {
// Restore the Select2 AMD loader so it can be used
// Needed mostly in the language files, where the loader is not inserted
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
@ -30,13 +44,11 @@
var S2;(function () { if (!S2 || !S2.requirejs) {
if (!S2) { S2 = {}; } else { require = S2; }
/**
* @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/almond for details
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
*/
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */
var requirejs, require, define;
@ -64,60 +76,58 @@ var requirejs, require, define;
*/
function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
foundI, foundStarMap, starI, i, j, part,
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
baseParts = baseName && baseName.split("/"),
map = config.map,
starMap = (map && map['*']) || {};
//Adjust any relative paths.
if (name && name.charAt(0) === ".") {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
name = name.split('/');
lastIndex = name.length - 1;
if (name) {
name = name.split('/');
lastIndex = name.length - 1;
// Node .js allowance:
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
}
// If wanting node ID compatibility, strip .js from end
// of IDs. Have to do this here, and not in nameToUrl
// because node allows either .js or non .js to map
// to same file.
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
}
//Lop off the last part of baseParts, so that . matches the
//"directory" and not name of the baseName's module. For instance,
//baseName of "one/two/three", maps to "one/two/three.js", but we
//want the directory, "one/two" for this normalization.
name = baseParts.slice(0, baseParts.length - 1).concat(name);
// Starts with a '.' so need the baseName
if (name[0].charAt(0) === '.' && baseParts) {
//Convert baseName to array, and lop off the last part,
//so that . matches that 'directory' and not name of the baseName's
//module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
name = normalizedBaseParts.concat(name);
}
//start trimDots
for (i = 0; i < name.length; i += 1) {
part = name[i];
if (part === ".") {
name.splice(i, 1);
i -= 1;
} else if (part === "..") {
if (i === 1 && (name[2] === '..' || name[0] === '..')) {
//End of the line. Keep at least one non-dot
//path segment at the front so it can be mapped
//correctly to disk. Otherwise, there is likely
//no path mapping for a path starting with '..'.
//This can still fail, but catches the most reasonable
//uses of ..
break;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
//start trimDots
for (i = 0; i < name.length; i++) {
part = name[i];
if (part === '.') {
name.splice(i, 1);
i -= 1;
} else if (part === '..') {
// If at the start, or previous value is still ..,
// keep them so that when converted to a path it may
// still work when converted to a path, even though
// as an ID it is less than ideal. In larger point
// releases, may be better to just kick out an error.
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
continue;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
}
//end trimDots
name = name.join("/");
} else if (name.indexOf('./') === 0) {
// No baseName, so this is ID is resolved relative
// to baseUrl, pull off the leading dot.
name = name.substring(2);
}
//end trimDots
name = name.join('/');
}
//Apply map config if available.
@ -230,32 +240,39 @@ var requirejs, require, define;
return [prefix, name];
}
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/**
* Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin
* too, as an optimization.
*/
makeMap = function (name, relName) {
makeMap = function (name, relParts) {
var plugin,
parts = splitPrefix(name),
prefix = parts[0];
prefix = parts[0],
relResourceName = relParts[1];
name = parts[1];
if (prefix) {
prefix = normalize(prefix, relName);
prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix);
}
//Normalize according
if (prefix) {
if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName));
name = plugin.normalize(name, makeNormalize(relResourceName));
} else {
name = normalize(name, relName);
name = normalize(name, relResourceName);
}
} else {
name = normalize(name, relName);
name = normalize(name, relResourceName);
parts = splitPrefix(name);
prefix = parts[0];
name = parts[1];
@ -302,13 +319,14 @@ var requirejs, require, define;
};
main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i,
var cjsModule, depName, ret, map, i, relParts,
args = [],
callbackType = typeof callback,
usingExports;
//Use name if no relName
relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') {
@ -317,7 +335,7 @@ var requirejs, require, define;
//Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName);
map = makeMap(deps[i], relParts);
depName = map.f;
//Fast path CommonJS standard dependencies.
@ -373,7 +391,7 @@ var requirejs, require, define;
//deps arg is the module name, and second arg (if passed)
//is just the relName.
//Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f);
return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) {
//deps is a config object, not an array.
config = deps;
@ -737,6 +755,12 @@ S2.define('select2/utils',[
});
};
Utils.entityDecode = function (html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
// Append an array of jQuery nodes to a given element.
Utils.appendMany = function ($element, $nodes) {
// jQuery 1.7.x does not support $.fn.append() with an array
@ -754,6 +778,14 @@ S2.define('select2/utils',[
$element.append($nodes);
};
// Determine whether the browser is on a touchscreen device.
Utils.isTouchscreen = function() {
if ('undefined' === typeof Utils._isTouchscreenCache) {
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
}
return Utils._isTouchscreenCache;
}
return Utils;
});
@ -773,7 +805,7 @@ S2.define('select2/results',[
Results.prototype.render = function () {
var $results = $(
'<ul class="select2-results__options" role="tree"></ul>'
'<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
);
if (this.options.get('multiple')) {
@ -796,7 +828,7 @@ S2.define('select2/results',[
this.hideLoading();
var $message = $(
'<li role="treeitem" aria-live="assertive"' +
'<li role="alert" aria-live="assertive"' +
' class="select2-results__option"></li>'
);
@ -858,9 +890,9 @@ S2.define('select2/results',[
Results.prototype.highlightFirstItem = function () {
var $options = this.$results
.find('.select2-results__option[aria-selected]');
.find('.select2-results__option[data-selected]');
var $selected = $options.filter('[aria-selected=true]');
var $selected = $options.filter('[data-selected=true]');
// Check if there are any selected options
if ($selected.length > 0) {
@ -884,7 +916,7 @@ S2.define('select2/results',[
});
var $options = self.$results
.find('.select2-results__option[aria-selected]');
.find('.select2-results__option[data-selected]');
$options.each(function () {
var $option = $(this);
@ -896,9 +928,9 @@ S2.define('select2/results',[
if ((item.element != null && item.element.selected) ||
(item.element == null && $.inArray(id, selectedIds) > -1)) {
$option.attr('aria-selected', 'true');
$option.attr('data-selected', 'true');
} else {
$option.attr('aria-selected', 'false');
$option.attr('data-selected', 'false');
}
});
@ -930,17 +962,18 @@ S2.define('select2/results',[
option.className = 'select2-results__option';
var attrs = {
'role': 'treeitem',
'aria-selected': 'false'
'role': 'option',
'data-selected': 'false',
'tabindex': -1
};
if (data.disabled) {
delete attrs['aria-selected'];
delete attrs['data-selected'];
attrs['aria-disabled'] = 'true';
}
if (data.id == null) {
delete attrs['aria-selected'];
delete attrs['data-selected'];
}
if (data._resultId != null) {
@ -952,9 +985,8 @@ S2.define('select2/results',[
}
if (data.children) {
attrs.role = 'group';
attrs['aria-label'] = data.text;
delete attrs['aria-selected'];
delete attrs['data-selected'];
}
for (var attr in attrs) {
@ -971,6 +1003,7 @@ S2.define('select2/results',[
var $label = $(label);
this.template(data, label);
$label.attr('role', 'presentation');
var $children = [];
@ -983,10 +1016,11 @@ S2.define('select2/results',[
}
var $childrenContainer = $('<ul></ul>', {
'class': 'select2-results__options select2-results__options--nested'
'class': 'select2-results__options select2-results__options--nested',
'role': 'listbox'
});
$childrenContainer.append($children);
$option.attr('role', 'list');
$option.append(label);
$option.append($childrenContainer);
@ -1082,7 +1116,7 @@ S2.define('select2/results',[
var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') {
if ($highlighted.attr('data-selected') == 'true') {
self.trigger('close', {});
} else {
self.trigger('select', {
@ -1094,7 +1128,7 @@ S2.define('select2/results',[
container.on('results:previous', function () {
var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]');
var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted);
@ -1128,7 +1162,7 @@ S2.define('select2/results',[
container.on('results:next', function () {
var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]');
var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted);
@ -1156,7 +1190,8 @@ S2.define('select2/results',[
});
container.on('results:focus', function (params) {
params.element.addClass('select2-results__option--highlighted');
params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true');
self.$results.attr('aria-activedescendant', params.element.attr('id'));
});
container.on('results:message', function (params) {
@ -1188,13 +1223,13 @@ S2.define('select2/results',[
});
}
this.$results.on('mouseup', '.select2-results__option[aria-selected]',
this.$results.on('mouseup', '.select2-results__option[data-selected]',
function (evt) {
var $this = $(this);
var data = $this.data('data');
if ($this.attr('aria-selected') === 'true') {
if ($this.attr('data-selected') === 'true') {
if (self.options.get('multiple')) {
self.trigger('unselect', {
originalEvent: evt,
@ -1213,12 +1248,13 @@ S2.define('select2/results',[
});
});
this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
this.$results.on('mouseenter', '.select2-results__option[data-selected]',
function (evt) {
var data = $(this).data('data');
self.getHighlightedResults()
.removeClass('select2-results__option--highlighted');
.removeClass('select2-results__option--highlighted')
.attr('aria-selected', 'false');
self.trigger('results:focus', {
data: data,
@ -1245,7 +1281,7 @@ S2.define('select2/results',[
return;
}
var $options = this.$results.find('[aria-selected]');
var $options = this.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted);
@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[
BaseSelection.prototype.render = function () {
var $selection = $(
'<span class="select2-selection" role="combobox" ' +
'<span class="select2-selection" ' +
' aria-haspopup="true" aria-expanded="false">' +
'</span>'
);
@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[
var id = container.id + '-container';
var resultsId = container.id + '-results';
var searchHidden = this.options.get('minimumResultsForSearch') === Infinity;
this.container = container;
@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[
self.$selection.removeAttr('aria-activedescendant');
self.$selection.removeAttr('aria-owns');
self.$selection.focus();
// This needs to be delayed as the active element is the body when the
// key is pressed.
window.setTimeout(function () {
self.$selection.focus();
}, 1);
self._detachCloseHandler(container);
});
@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[
}
var $element = $this.data('element');
$element.select2('close');
// Remove any focus when dropdown is closed by clicking outside the select area.
// Timeout of 1 required for close to finish wrapping up.
setTimeout(function(){
$this.find('*:focus').blur();
$target.focus();
}, 1);
});
});
};
@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[
var id = container.id + '-container';
this.$selection.find('.select2-selection__rendered').attr('id', id);
this.$selection.attr('aria-labelledby', id);
this.$selection.find('.select2-selection__rendered')
.attr('id', id)
.attr('role', 'textbox')
.attr('aria-readonly', 'true');
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox');
this.$selection.on('mousedown', function (evt) {
// Only respond to left clicks
@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[
// User focuses on the container
});
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
this.$selection.on('blur', function (evt) {
// User exits the container
});
@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[
var selection = data[0];
var $rendered = this.$selection.find('.select2-selection__rendered');
var formatted = this.display(selection, $rendered);
var formatted = Utils.entityDecode(this.display(selection, $rendered));
$rendered.empty().append(formatted);
$rendered.empty().text(formatted);
$rendered.prop('title', selection.title || selection.text);
};
@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[
$selection.addClass('select2-selection--multiple');
$selection.html(
'<ul class="select2-selection__rendered"></ul>'
'<ul class="select2-selection__rendered" aria-live="polite" aria-relevant="additions removals" aria-atomic="true"></ul>'
);
return $selection;
@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[
});
}
);
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
// Focus on the search field when the container is focused instead of the main container.
container.on( 'focus', function(){
self.focusOnSearch();
});
};
MultipleSelection.prototype.clear = function () {
@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () {
var $container = $(
'<li class="select2-selection__choice">' +
'<span class="select2-selection__choice__remove" role="presentation">' +
'<span class="select2-selection__choice__remove" role="presentation" aria-hidden="true">' +
'&times;' +
'</span>' +
'</li>'
@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[
return $container;
};
/**
* Focus on the search field instead of the main multiselect container.
*/
MultipleSelection.prototype.focusOnSearch = function() {
var self = this;
if ('undefined' !== typeof self.$search) {
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
// Prevent the dropdown opening again when focused from this.
// This gets reset automatically when focus is triggered.
self._keyUpPrevented = true;
self.$search.focus();
}, 1);
}
}
MultipleSelection.prototype.update = function (data) {
this.clear();
@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[
var selection = data[d];
var $selection = this.selectionContainer();
var removeItemTag = $selection.html();
var formatted = this.display(selection, $selection);
if ('string' === typeof formatted) {
formatted = Utils.entityDecode(formatted.trim());
}
$selection.append(formatted);
$selection.text(formatted);
$selection.prepend(removeItemTag);
$selection.prop('title', selection.title || selection.text);
$selection.data('data', selection);
@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
var $placeholder = this.selectionContainer();
$placeholder.html(this.display(placeholder));
$placeholder.text(Utils.entityDecode(this.display(placeholder)));
$placeholder.addClass('select2-selection__placeholder')
.removeClass('select2-selection__choice');
@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[
Search.prototype.render = function (decorated) {
var $search = $(
'<li class="select2-search select2-search--inline">' +
'<input class="select2-search__field" type="search" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
'<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
'</li>'
);
@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[
Search.prototype.bind = function (decorated, container, $container) {
var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container);
container.on('open', function () {
self.$search.attr('aria-owns', resultsId);
self.$search.trigger('focus');
});
container.on('close', function () {
self.$search.val('');
self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.trigger('focus');
});
@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[
});
container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.id);
self.$search.attr('aria-activedescendant', params.data._resultId);
});
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[
evt.preventDefault();
}
} else if (evt.which === KEYS.ENTER) {
container.open();
evt.preventDefault();
}
});
@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[
};
BaseAdapter.prototype.generateResultId = function (container, data) {
var id = container.id + '-result-';
var id = '';
if (container != null) {
id += container.id
} else {
id += Utils.generateChars(4);
}
id += '-result-';
id += Utils.generateChars(4);
if (data.id != null) {
@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[
}
}
if (data.id) {
if (data.id !== undefined) {
option.value = data.id;
}
@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[
item.text = item.text.toString();
}
if (item._resultId == null && item.id && this.container != null) {
if (item._resultId == null && item.id) {
item._resultId = this.generateResultId(this.container, item);
}
@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[
}
callback(results);
self.container.focusOnActiveElement();
}, function () {
// Attempt to detect if a request was aborted
// Only works if the transport exposes a status property
@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[
}, true)
);
var checkText = option.text === params.term;
var optionText = (option.text || '').toUpperCase();
var paramsTerm = (params.term || '').toUpperCase();
var checkText = optionText === paramsTerm;
if (checkText || checkChildren) {
if (child) {
@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[
var $search = $(
'<span class="select2-search select2-search--dropdown">' +
'<input class="select2-search__field" type="search" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
' spellcheck="false" role="textbox" />' +
'<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' +
'</span>'
);
@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[
Search.prototype.bind = function (decorated, container, $container) {
var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container);
@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[
container.on('open', function () {
self.$search.attr('tabindex', 0);
self.$search.attr('aria-owns', resultsId);
self.$search.focus();
window.setTimeout(function () {
@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[
container.on('close', function () {
self.$search.attr('tabindex', -1);
self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.val('');
});
container.on('focus', function () {
if (container.isOpen()) {
if (!container.isOpen()) {
self.$search.focus();
}
});
@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[
}
}
});
container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.data._resultId);
});
};
Search.prototype.handleSearch = function (evt) {
@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[
var $option = $(
'<li ' +
'class="select2-results__option select2-results__option--load-more"' +
'role="treeitem" aria-disabled="true"></li>'
'role="option" aria-disabled="true"></li>'
);
var message = this.options.get('translations').get('loadingMore');
@ -5344,16 +5469,22 @@ S2.define('select2/core',[
});
});
this.on('keypress', function (evt) {
var key = evt.which;
this.on('open', function(){
// Focus on the active element when opening dropdown.
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
self.focusOnActiveElement();
}, 1);
});
$(document).on('keydown', function (evt) {
var key = evt.which;
if (self.isOpen()) {
if (key === KEYS.ESC || key === KEYS.TAB ||
(key === KEYS.UP && evt.altKey)) {
if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) {
self.close();
evt.preventDefault();
} else if (key === KEYS.ENTER) {
} else if (key === KEYS.ENTER || key === KEYS.TAB) {
self.trigger('results:select', {});
evt.preventDefault();
@ -5370,17 +5501,42 @@ S2.define('select2/core',[
evt.preventDefault();
}
} else {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
(key === KEYS.DOWN && evt.altKey)) {
self.open();
var $searchField = self.$dropdown.find('.select2-search__field');
if (! $searchField.length) {
$searchField = self.$container.find('.select2-search__field');
}
// Move the focus to the selected element on keyboard navigation.
// Required for screen readers to work properly.
if (key === KEYS.DOWN || key === KEYS.UP) {
self.focusOnActiveElement();
} else {
// Focus on the search if user starts typing.
$searchField.focus();
// Focus back to active selection when finished typing.
// Small delay so typed character can be read by screen reader.
setTimeout(function(){
self.focusOnActiveElement();
}, 1000);
}
} else if (self.hasFocus()) {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
key === KEYS.DOWN) {
self.open();
evt.preventDefault();
}
}
});
};
Select2.prototype.focusOnActiveElement = function () {
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
if (this.isOpen() && ! Utils.isTouchscreen()) {
this.$results.find('li.select2-results__option--highlighted').focus();
}
};
Select2.prototype._syncAttributes = function () {
this.options.set('disabled', this.$element.prop('disabled'));
@ -6364,11 +6520,11 @@ S2.define('jquery.select2',[
'./select2/core',
'./select2/defaults'
], function ($, _, Select2, Defaults) {
if ($.fn.select2 == null) {
if ($.fn.selectWoo == null) {
// All methods that should return the element
var thisMethods = ['open', 'close', 'destroy'];
$.fn.select2 = function (options) {
$.fn.selectWoo = function (options) {
options = options || {};
if (typeof options === 'object') {
@ -6408,10 +6564,17 @@ S2.define('jquery.select2',[
};
}
if ($.fn.select2.defaults == null) {
$.fn.select2.defaults = Defaults;
if ($.fn.select2 != null && $.fn.select2.defaults != null) {
$.fn.selectWoo.defaults = $.fn.select2.defaults;
}
if ($.fn.selectWoo.defaults == null) {
$.fn.selectWoo.defaults = Defaults;
}
// Also register selectWoo under select2 if select2 is not already present.
$.fn.select2 = $.fn.select2 || $.fn.selectWoo;
return Select2;
});
@ -6430,6 +6593,7 @@ S2.define('jquery.select2',[
// This allows Select2 to use the internal loader outside of this file, such
// as in the language files.
jQuery.fn.select2.amd = S2;
jQuery.fn.selectWoo.amd = S2;
// Return the Select2 instance for anyone who is importing it.
return select2;

View File

@ -1,27 +1,41 @@
/*!
* Select2 4.0.3
* https://select2.github.io
* SelectWoo 1.0.9
* https://github.com/woocommerce/selectWoo
*
* Released under the MIT license
* https://github.com/select2/select2/blob/master/LICENSE.md
* https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
factory(require('jquery'));
module.exports = function (root, jQuery) {
if (jQuery === undefined) {
// require('jQuery') returns a factory that requires window to
// build a jQuery instance, we normalize how we use modules
// that require this pattern but the window provided is a noop
// if it's defined (how jquery works)
if (typeof window !== 'undefined') {
jQuery = require('jquery');
}
else {
jQuery = require('jquery')(root);
}
}
factory(jQuery);
return jQuery;
};
} else {
// Browser globals
factory(jQuery);
}
}(function (jQuery) {
} (function (jQuery) {
// This is needed so we can catch the AMD loader configuration and use it
// The inner file should be wrapped (by `banner.start.js`) in a function that
// returns the AMD loader references.
var S2 =
(function () {
var S2 =(function () {
// Restore the Select2 AMD loader so it can be used
// Needed mostly in the language files, where the loader is not inserted
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
@ -30,13 +44,11 @@
var S2;(function () { if (!S2 || !S2.requirejs) {
if (!S2) { S2 = {}; } else { require = S2; }
/**
* @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/almond for details
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
*/
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */
var requirejs, require, define;
@ -64,60 +76,58 @@ var requirejs, require, define;
*/
function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
foundI, foundStarMap, starI, i, j, part,
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
baseParts = baseName && baseName.split("/"),
map = config.map,
starMap = (map && map['*']) || {};
//Adjust any relative paths.
if (name && name.charAt(0) === ".") {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
name = name.split('/');
lastIndex = name.length - 1;
if (name) {
name = name.split('/');
lastIndex = name.length - 1;
// Node .js allowance:
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
}
// If wanting node ID compatibility, strip .js from end
// of IDs. Have to do this here, and not in nameToUrl
// because node allows either .js or non .js to map
// to same file.
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
}
//Lop off the last part of baseParts, so that . matches the
//"directory" and not name of the baseName's module. For instance,
//baseName of "one/two/three", maps to "one/two/three.js", but we
//want the directory, "one/two" for this normalization.
name = baseParts.slice(0, baseParts.length - 1).concat(name);
// Starts with a '.' so need the baseName
if (name[0].charAt(0) === '.' && baseParts) {
//Convert baseName to array, and lop off the last part,
//so that . matches that 'directory' and not name of the baseName's
//module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
name = normalizedBaseParts.concat(name);
}
//start trimDots
for (i = 0; i < name.length; i += 1) {
part = name[i];
if (part === ".") {
name.splice(i, 1);
i -= 1;
} else if (part === "..") {
if (i === 1 && (name[2] === '..' || name[0] === '..')) {
//End of the line. Keep at least one non-dot
//path segment at the front so it can be mapped
//correctly to disk. Otherwise, there is likely
//no path mapping for a path starting with '..'.
//This can still fail, but catches the most reasonable
//uses of ..
break;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
//start trimDots
for (i = 0; i < name.length; i++) {
part = name[i];
if (part === '.') {
name.splice(i, 1);
i -= 1;
} else if (part === '..') {
// If at the start, or previous value is still ..,
// keep them so that when converted to a path it may
// still work when converted to a path, even though
// as an ID it is less than ideal. In larger point
// releases, may be better to just kick out an error.
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
continue;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
}
//end trimDots
name = name.join("/");
} else if (name.indexOf('./') === 0) {
// No baseName, so this is ID is resolved relative
// to baseUrl, pull off the leading dot.
name = name.substring(2);
}
//end trimDots
name = name.join('/');
}
//Apply map config if available.
@ -230,32 +240,39 @@ var requirejs, require, define;
return [prefix, name];
}
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/**
* Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin
* too, as an optimization.
*/
makeMap = function (name, relName) {
makeMap = function (name, relParts) {
var plugin,
parts = splitPrefix(name),
prefix = parts[0];
prefix = parts[0],
relResourceName = relParts[1];
name = parts[1];
if (prefix) {
prefix = normalize(prefix, relName);
prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix);
}
//Normalize according
if (prefix) {
if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName));
name = plugin.normalize(name, makeNormalize(relResourceName));
} else {
name = normalize(name, relName);
name = normalize(name, relResourceName);
}
} else {
name = normalize(name, relName);
name = normalize(name, relResourceName);
parts = splitPrefix(name);
prefix = parts[0];
name = parts[1];
@ -302,13 +319,14 @@ var requirejs, require, define;
};
main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i,
var cjsModule, depName, ret, map, i, relParts,
args = [],
callbackType = typeof callback,
usingExports;
//Use name if no relName
relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') {
@ -317,7 +335,7 @@ var requirejs, require, define;
//Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName);
map = makeMap(deps[i], relParts);
depName = map.f;
//Fast path CommonJS standard dependencies.
@ -373,7 +391,7 @@ var requirejs, require, define;
//deps arg is the module name, and second arg (if passed)
//is just the relName.
//Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f);
return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) {
//deps is a config object, not an array.
config = deps;
@ -737,6 +755,12 @@ S2.define('select2/utils',[
});
};
Utils.entityDecode = function (html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
// Append an array of jQuery nodes to a given element.
Utils.appendMany = function ($element, $nodes) {
// jQuery 1.7.x does not support $.fn.append() with an array
@ -754,6 +778,14 @@ S2.define('select2/utils',[
$element.append($nodes);
};
// Determine whether the browser is on a touchscreen device.
Utils.isTouchscreen = function() {
if ('undefined' === typeof Utils._isTouchscreenCache) {
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
}
return Utils._isTouchscreenCache;
}
return Utils;
});
@ -773,7 +805,7 @@ S2.define('select2/results',[
Results.prototype.render = function () {
var $results = $(
'<ul class="select2-results__options" role="tree"></ul>'
'<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
);
if (this.options.get('multiple')) {
@ -796,7 +828,7 @@ S2.define('select2/results',[
this.hideLoading();
var $message = $(
'<li role="treeitem" aria-live="assertive"' +
'<li role="alert" aria-live="assertive"' +
' class="select2-results__option"></li>'
);
@ -858,9 +890,9 @@ S2.define('select2/results',[
Results.prototype.highlightFirstItem = function () {
var $options = this.$results
.find('.select2-results__option[aria-selected]');
.find('.select2-results__option[data-selected]');
var $selected = $options.filter('[aria-selected=true]');
var $selected = $options.filter('[data-selected=true]');
// Check if there are any selected options
if ($selected.length > 0) {
@ -884,7 +916,7 @@ S2.define('select2/results',[
});
var $options = self.$results
.find('.select2-results__option[aria-selected]');
.find('.select2-results__option[data-selected]');
$options.each(function () {
var $option = $(this);
@ -896,9 +928,9 @@ S2.define('select2/results',[
if ((item.element != null && item.element.selected) ||
(item.element == null && $.inArray(id, selectedIds) > -1)) {
$option.attr('aria-selected', 'true');
$option.attr('data-selected', 'true');
} else {
$option.attr('aria-selected', 'false');
$option.attr('data-selected', 'false');
}
});
@ -930,17 +962,18 @@ S2.define('select2/results',[
option.className = 'select2-results__option';
var attrs = {
'role': 'treeitem',
'aria-selected': 'false'
'role': 'option',
'data-selected': 'false',
'tabindex': -1
};
if (data.disabled) {
delete attrs['aria-selected'];
delete attrs['data-selected'];
attrs['aria-disabled'] = 'true';
}
if (data.id == null) {
delete attrs['aria-selected'];
delete attrs['data-selected'];
}
if (data._resultId != null) {
@ -952,9 +985,8 @@ S2.define('select2/results',[
}
if (data.children) {
attrs.role = 'group';
attrs['aria-label'] = data.text;
delete attrs['aria-selected'];
delete attrs['data-selected'];
}
for (var attr in attrs) {
@ -971,6 +1003,7 @@ S2.define('select2/results',[
var $label = $(label);
this.template(data, label);
$label.attr('role', 'presentation');
var $children = [];
@ -983,10 +1016,11 @@ S2.define('select2/results',[
}
var $childrenContainer = $('<ul></ul>', {
'class': 'select2-results__options select2-results__options--nested'
'class': 'select2-results__options select2-results__options--nested',
'role': 'listbox'
});
$childrenContainer.append($children);
$option.attr('role', 'list');
$option.append(label);
$option.append($childrenContainer);
@ -1082,7 +1116,7 @@ S2.define('select2/results',[
var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') {
if ($highlighted.attr('data-selected') == 'true') {
self.trigger('close', {});
} else {
self.trigger('select', {
@ -1094,7 +1128,7 @@ S2.define('select2/results',[
container.on('results:previous', function () {
var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]');
var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted);
@ -1128,7 +1162,7 @@ S2.define('select2/results',[
container.on('results:next', function () {
var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]');
var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted);
@ -1156,7 +1190,8 @@ S2.define('select2/results',[
});
container.on('results:focus', function (params) {
params.element.addClass('select2-results__option--highlighted');
params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true');
self.$results.attr('aria-activedescendant', params.element.attr('id'));
});
container.on('results:message', function (params) {
@ -1188,13 +1223,13 @@ S2.define('select2/results',[
});
}
this.$results.on('mouseup', '.select2-results__option[aria-selected]',
this.$results.on('mouseup', '.select2-results__option[data-selected]',
function (evt) {
var $this = $(this);
var data = $this.data('data');
if ($this.attr('aria-selected') === 'true') {
if ($this.attr('data-selected') === 'true') {
if (self.options.get('multiple')) {
self.trigger('unselect', {
originalEvent: evt,
@ -1213,12 +1248,13 @@ S2.define('select2/results',[
});
});
this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
this.$results.on('mouseenter', '.select2-results__option[data-selected]',
function (evt) {
var data = $(this).data('data');
self.getHighlightedResults()
.removeClass('select2-results__option--highlighted');
.removeClass('select2-results__option--highlighted')
.attr('aria-selected', 'false');
self.trigger('results:focus', {
data: data,
@ -1245,7 +1281,7 @@ S2.define('select2/results',[
return;
}
var $options = this.$results.find('[aria-selected]');
var $options = this.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted);
@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[
BaseSelection.prototype.render = function () {
var $selection = $(
'<span class="select2-selection" role="combobox" ' +
'<span class="select2-selection" ' +
' aria-haspopup="true" aria-expanded="false">' +
'</span>'
);
@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[
var id = container.id + '-container';
var resultsId = container.id + '-results';
var searchHidden = this.options.get('minimumResultsForSearch') === Infinity;
this.container = container;
@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[
self.$selection.removeAttr('aria-activedescendant');
self.$selection.removeAttr('aria-owns');
self.$selection.focus();
// This needs to be delayed as the active element is the body when the
// key is pressed.
window.setTimeout(function () {
self.$selection.focus();
}, 1);
self._detachCloseHandler(container);
});
@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[
}
var $element = $this.data('element');
$element.select2('close');
// Remove any focus when dropdown is closed by clicking outside the select area.
// Timeout of 1 required for close to finish wrapping up.
setTimeout(function(){
$this.find('*:focus').blur();
$target.focus();
}, 1);
});
});
};
@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[
var id = container.id + '-container';
this.$selection.find('.select2-selection__rendered').attr('id', id);
this.$selection.attr('aria-labelledby', id);
this.$selection.find('.select2-selection__rendered')
.attr('id', id)
.attr('role', 'textbox')
.attr('aria-readonly', 'true');
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox');
this.$selection.on('mousedown', function (evt) {
// Only respond to left clicks
@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[
// User focuses on the container
});
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
this.$selection.on('blur', function (evt) {
// User exits the container
});
@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[
var selection = data[0];
var $rendered = this.$selection.find('.select2-selection__rendered');
var formatted = this.display(selection, $rendered);
var formatted = Utils.entityDecode(this.display(selection, $rendered));
$rendered.empty().append(formatted);
$rendered.empty().text(formatted);
$rendered.prop('title', selection.title || selection.text);
};
@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[
$selection.addClass('select2-selection--multiple');
$selection.html(
'<ul class="select2-selection__rendered"></ul>'
'<ul class="select2-selection__rendered" aria-live="polite" aria-relevant="additions removals" aria-atomic="true"></ul>'
);
return $selection;
@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[
});
}
);
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
// Focus on the search field when the container is focused instead of the main container.
container.on( 'focus', function(){
self.focusOnSearch();
});
};
MultipleSelection.prototype.clear = function () {
@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () {
var $container = $(
'<li class="select2-selection__choice">' +
'<span class="select2-selection__choice__remove" role="presentation">' +
'<span class="select2-selection__choice__remove" role="presentation" aria-hidden="true">' +
'&times;' +
'</span>' +
'</li>'
@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[
return $container;
};
/**
* Focus on the search field instead of the main multiselect container.
*/
MultipleSelection.prototype.focusOnSearch = function() {
var self = this;
if ('undefined' !== typeof self.$search) {
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
// Prevent the dropdown opening again when focused from this.
// This gets reset automatically when focus is triggered.
self._keyUpPrevented = true;
self.$search.focus();
}, 1);
}
}
MultipleSelection.prototype.update = function (data) {
this.clear();
@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[
var selection = data[d];
var $selection = this.selectionContainer();
var removeItemTag = $selection.html();
var formatted = this.display(selection, $selection);
if ('string' === typeof formatted) {
formatted = Utils.entityDecode(formatted.trim());
}
$selection.append(formatted);
$selection.text(formatted);
$selection.prepend(removeItemTag);
$selection.prop('title', selection.title || selection.text);
$selection.data('data', selection);
@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
var $placeholder = this.selectionContainer();
$placeholder.html(this.display(placeholder));
$placeholder.text(Utils.entityDecode(this.display(placeholder)));
$placeholder.addClass('select2-selection__placeholder')
.removeClass('select2-selection__choice');
@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[
Search.prototype.render = function (decorated) {
var $search = $(
'<li class="select2-search select2-search--inline">' +
'<input class="select2-search__field" type="search" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
'<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
'</li>'
);
@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[
Search.prototype.bind = function (decorated, container, $container) {
var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container);
container.on('open', function () {
self.$search.attr('aria-owns', resultsId);
self.$search.trigger('focus');
});
container.on('close', function () {
self.$search.val('');
self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.trigger('focus');
});
@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[
});
container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.id);
self.$search.attr('aria-activedescendant', params.data._resultId);
});
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[
evt.preventDefault();
}
} else if (evt.which === KEYS.ENTER) {
container.open();
evt.preventDefault();
}
});
@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[
};
BaseAdapter.prototype.generateResultId = function (container, data) {
var id = container.id + '-result-';
var id = '';
if (container != null) {
id += container.id
} else {
id += Utils.generateChars(4);
}
id += '-result-';
id += Utils.generateChars(4);
if (data.id != null) {
@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[
}
}
if (data.id) {
if (data.id !== undefined) {
option.value = data.id;
}
@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[
item.text = item.text.toString();
}
if (item._resultId == null && item.id && this.container != null) {
if (item._resultId == null && item.id) {
item._resultId = this.generateResultId(this.container, item);
}
@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[
}
callback(results);
self.container.focusOnActiveElement();
}, function () {
// Attempt to detect if a request was aborted
// Only works if the transport exposes a status property
@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[
}, true)
);
var checkText = option.text === params.term;
var optionText = (option.text || '').toUpperCase();
var paramsTerm = (params.term || '').toUpperCase();
var checkText = optionText === paramsTerm;
if (checkText || checkChildren) {
if (child) {
@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[
var $search = $(
'<span class="select2-search select2-search--dropdown">' +
'<input class="select2-search__field" type="search" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
' spellcheck="false" role="textbox" />' +
'<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' +
'</span>'
);
@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[
Search.prototype.bind = function (decorated, container, $container) {
var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container);
@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[
container.on('open', function () {
self.$search.attr('tabindex', 0);
self.$search.attr('aria-owns', resultsId);
self.$search.focus();
window.setTimeout(function () {
@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[
container.on('close', function () {
self.$search.attr('tabindex', -1);
self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.val('');
});
container.on('focus', function () {
if (container.isOpen()) {
if (!container.isOpen()) {
self.$search.focus();
}
});
@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[
}
}
});
container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.data._resultId);
});
};
Search.prototype.handleSearch = function (evt) {
@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[
var $option = $(
'<li ' +
'class="select2-results__option select2-results__option--load-more"' +
'role="treeitem" aria-disabled="true"></li>'
'role="option" aria-disabled="true"></li>'
);
var message = this.options.get('translations').get('loadingMore');
@ -5344,16 +5469,22 @@ S2.define('select2/core',[
});
});
this.on('keypress', function (evt) {
var key = evt.which;
this.on('open', function(){
// Focus on the active element when opening dropdown.
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
self.focusOnActiveElement();
}, 1);
});
$(document).on('keydown', function (evt) {
var key = evt.which;
if (self.isOpen()) {
if (key === KEYS.ESC || key === KEYS.TAB ||
(key === KEYS.UP && evt.altKey)) {
if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) {
self.close();
evt.preventDefault();
} else if (key === KEYS.ENTER) {
} else if (key === KEYS.ENTER || key === KEYS.TAB) {
self.trigger('results:select', {});
evt.preventDefault();
@ -5370,17 +5501,42 @@ S2.define('select2/core',[
evt.preventDefault();
}
} else {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
(key === KEYS.DOWN && evt.altKey)) {
self.open();
var $searchField = self.$dropdown.find('.select2-search__field');
if (! $searchField.length) {
$searchField = self.$container.find('.select2-search__field');
}
// Move the focus to the selected element on keyboard navigation.
// Required for screen readers to work properly.
if (key === KEYS.DOWN || key === KEYS.UP) {
self.focusOnActiveElement();
} else {
// Focus on the search if user starts typing.
$searchField.focus();
// Focus back to active selection when finished typing.
// Small delay so typed character can be read by screen reader.
setTimeout(function(){
self.focusOnActiveElement();
}, 1000);
}
} else if (self.hasFocus()) {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
key === KEYS.DOWN) {
self.open();
evt.preventDefault();
}
}
});
};
Select2.prototype.focusOnActiveElement = function () {
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
if (this.isOpen() && ! Utils.isTouchscreen()) {
this.$results.find('li.select2-results__option--highlighted').focus();
}
};
Select2.prototype._syncAttributes = function () {
this.options.set('disabled', this.$element.prop('disabled'));
@ -5653,11 +5809,11 @@ S2.define('jquery.select2',[
'./select2/core',
'./select2/defaults'
], function ($, _, Select2, Defaults) {
if ($.fn.select2 == null) {
if ($.fn.selectWoo == null) {
// All methods that should return the element
var thisMethods = ['open', 'close', 'destroy'];
$.fn.select2 = function (options) {
$.fn.selectWoo = function (options) {
options = options || {};
if (typeof options === 'object') {
@ -5697,10 +5853,17 @@ S2.define('jquery.select2',[
};
}
if ($.fn.select2.defaults == null) {
$.fn.select2.defaults = Defaults;
if ($.fn.select2 != null && $.fn.select2.defaults != null) {
$.fn.selectWoo.defaults = $.fn.select2.defaults;
}
if ($.fn.selectWoo.defaults == null) {
$.fn.selectWoo.defaults = Defaults;
}
// Also register selectWoo under select2 if select2 is not already present.
$.fn.select2 = $.fn.select2 || $.fn.selectWoo;
return Select2;
});
@ -5719,6 +5882,7 @@ S2.define('jquery.select2',[
// This allows Select2 to use the internal loader outside of this file, such
// as in the language files.
jQuery.fn.select2.amd = S2;
jQuery.fn.selectWoo.amd = S2;
// Return the Select2 instance for anyone who is importing it.
return select2;

View File

@ -1,5 +1,5 @@
/*!
* SelectWoo 1.0.6
* SelectWoo 1.0.9
* https://github.com/woocommerce/selectWoo
*
* Released under the MIT license
@ -755,8 +755,8 @@ S2.define('select2/utils',[
});
};
Utils.entityDecode = function(html) {
var txt = document.createElement("textarea");
Utils.entityDecode = function (html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[
.attr('id', id)
.attr('role', 'textbox')
.attr('aria-readonly', 'true');
this.$selection.attr('aria-labelledby', id);
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox');
@ -4398,6 +4405,7 @@ S2.define('select2/dropdown/attachBody',[
var parentOffset = $offsetParent.offset();
css.top -= parentOffset.top;
css.left -= parentOffset.left;
if (!isCurrentlyAbove && !isCurrentlyBelow) {
@ -4412,7 +4420,7 @@ S2.define('select2/dropdown/attachBody',[
if (newDirection == 'above' ||
(isCurrentlyAbove && newDirection !== 'below')) {
css.top = container.top - dropdown.height;
css.top = container.top - parentOffset.top - dropdown.height;
}
if (newDirection != null) {

View File

@ -1,5 +1,5 @@
/*!
* SelectWoo 1.0.6
* SelectWoo 1.0.9
* https://github.com/woocommerce/selectWoo
*
* Released under the MIT license
@ -755,8 +755,8 @@ S2.define('select2/utils',[
});
};
Utils.entityDecode = function(html) {
var txt = document.createElement("textarea");
Utils.entityDecode = function (html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[
.attr('id', id)
.attr('role', 'textbox')
.attr('aria-readonly', 'true');
this.$selection.attr('aria-labelledby', id);
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox');

View File

@ -172,7 +172,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
*/
private function populate_payment_tasks() {
$is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' );
$country = explode( ':', get_option( 'woocommerce_default_country', '' ) )[0];
$country = explode( ':', get_option( 'woocommerce_default_country', 'US:CA' ) )[0];
// woocommerce-payments requires its plugin activated and country must be US.
if ( ! $is_woo_payment_installed || 'US' !== $country ) {

View File

@ -430,7 +430,10 @@ class WC_Admin_Post_Types {
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$product->set_manage_stock( $manage_stock );
$product->set_backorders( $backorders );
if ( 'external' !== $product->get_type() ) {
$product->set_backorders( $backorders );
}
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
$stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : '';
@ -550,7 +553,10 @@ class WC_Admin_Post_Types {
$stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();
$product->set_manage_stock( $manage_stock );
$product->set_backorders( $backorders );
if ( 'external' !== $product->get_type() ) {
$product->set_backorders( $backorders );
}
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
$change_stock = absint( $request_data['change_stock'] );

View File

@ -481,7 +481,7 @@ class WC_Admin_Setup_Wizard {
$state = WC()->countries->get_base_state();
$country = WC()->countries->get_base_country();
$postcode = WC()->countries->get_base_postcode();
$currency = get_option( 'woocommerce_currency', 'GBP' );
$currency = get_option( 'woocommerce_currency', 'USD' );
$product_type = get_option( 'woocommerce_product_type', 'both' );
$sell_in_person = get_option( 'woocommerce_sell_in_person', 'none_selected' );

View File

@ -11,6 +11,8 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
/**
* WC_Admin_Taxonomies class.
*/
@ -49,6 +51,12 @@ class WC_Admin_Taxonomies {
// Category/term ordering.
add_action( 'create_term', array( $this, 'create_term' ), 5, 3 );
add_action(
'delete_product_cat',
function() {
wc_get_container()->get( AssignDefaultCategory::class )->schedule_action();
}
);
// Add form.
add_action( 'product_cat_add_form_fields', array( $this, 'add_category_fields' ) );
@ -91,7 +99,7 @@ class WC_Admin_Taxonomies {
* @param string $taxonomy Taxonomy slug.
*/
public function create_term( $term_id, $tt_id = '', $taxonomy = '' ) {
if ( 'product_cat' != $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
if ( 'product_cat' !== $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
return;
}

View File

@ -46,12 +46,19 @@ class WC_Settings_Emails extends WC_Settings_Page {
* @return array
*/
public function get_settings() {
$desc_help_text = sprintf(
/* translators: %1$s: Link to WP Mail Logging plugin, %2$s: Link to Email FAQ support page. */
__( 'To ensure your store&rsquo;s notifications arrive in your and your customers&rsquo; inboxes, we recommend connecting your email address to your domain and setting up a dedicated SMTP server. If something doesn&rsquo;t seem to be sending correctly, install the <a href="%1$s">WP Mail Logging Plugin</a> or check the <a href="%2$s">Email FAQ page</a>.', 'woocommerce' ),
'https://wordpress.org/plugins/wp-mail-logging/',
'https://docs.woocommerce.com/document/email-faq'
);
$settings = apply_filters(
'woocommerce_email_settings',
array(
array(
'title' => __( 'Email notifications', 'woocommerce' ),
'desc' => __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.', 'woocommerce' ),
/* translators: %s: help description with link to WP Mail logging and support page. */
'desc' => sprintf( __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.<br>%s', 'woocommerce' ), $desc_help_text ),
'type' => 'title',
'id' => 'email_notification_settings',
),

View File

@ -81,7 +81,7 @@ class WC_Settings_General extends WC_Settings_Page {
'title' => __( 'Country / State', 'woocommerce' ),
'desc' => __( 'The country and state or province, if any, in which your business is located.', 'woocommerce' ),
'id' => 'woocommerce_default_country',
'default' => 'GB',
'default' => 'US:CA',
'type' => 'single_select_country',
'desc_tip' => true,
),
@ -229,7 +229,7 @@ class WC_Settings_General extends WC_Settings_Page {
'title' => __( 'Currency', 'woocommerce' ),
'desc' => __( 'This controls what currency prices are listed at in the catalog and which currency gateways will take payments in.', 'woocommerce' ),
'id' => 'woocommerce_currency',
'default' => 'GBP',
'default' => 'USD',
'type' => 'select',
'class' => 'wc-enhanced-select',
'desc_tip' => true,

View File

@ -224,7 +224,7 @@ class WC_Frontend_Scripts {
'selectWoo' => array(
'src' => self::get_asset_url( 'assets/js/selectWoo/selectWoo.full' . $suffix . '.js' ),
'deps' => array( 'jquery' ),
'version' => '1.0.6',
'version' => '1.0.9',
),
'wc-address-i18n' => array(
'src' => self::get_asset_url( 'assets/js/frontend/address-i18n' . $suffix . '.js' ),

View File

@ -9,6 +9,7 @@
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
use Automattic\WooCommerce\Proxies\LegacyProxy;
/**
@ -207,6 +208,7 @@ final class WooCommerce {
// These classes set up hooks on instantiation.
wc_get_container()->get( DownloadPermissionsAdjuster::class );
wc_get_container()->get( AssignDefaultCategory::class );
}
/**

View File

@ -6,6 +6,8 @@
* @package WooCommerce
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -13,10 +15,19 @@ defined( 'ABSPATH' ) || exit;
*/
class WC_Shop_Customizer {
/**
* Holds the instance of ThemeSupport to use.
*
* @var ThemeSupport $theme_support The instance of ThemeSupport to use.
*/
private $theme_support;
/**
* Constructor.
*/
public function __construct() {
$this->theme_support = wc_get_container()->get( ThemeSupport::class );
add_action( 'customize_register', array( $this, 'add_sections' ) );
add_action( 'customize_controls_print_styles', array( $this, 'add_styles' ) );
add_action( 'customize_controls_print_scripts', array( $this, 'add_scripts' ), 30 );
@ -545,11 +556,11 @@ class WC_Shop_Customizer {
)
);
if ( ! wc_get_theme_support( 'single_image_width' ) ) {
if ( ! $this->theme_support->has_option( 'single_image_width', false ) ) {
$wp_customize->add_setting(
'woocommerce_single_image_width',
array(
'default' => 600,
'default' => $this->theme_support->get_option( 'single_image_width', 600 ),
'type' => 'option',
'capability' => 'manage_woocommerce',
'sanitize_callback' => 'absint',
@ -573,11 +584,11 @@ class WC_Shop_Customizer {
);
}
if ( ! wc_get_theme_support( 'thumbnail_image_width' ) ) {
if ( ! $this->theme_support->has_option( 'thumbnail_image_width', false ) ) {
$wp_customize->add_setting(
'woocommerce_thumbnail_image_width',
array(
'default' => 300,
'default' => $this->theme_support->get_option( 'thumbnail_image_width', 300 ),
'type' => 'option',
'capability' => 'manage_woocommerce',
'sanitize_callback' => 'absint',
@ -769,7 +780,7 @@ class WC_Shop_Customizer {
);
} else {
$choose_pages = array(
'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ),
'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ),
);
}
$pages = get_pages(

View File

@ -10,6 +10,8 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
/**
* Terms controller class.
*/
@ -563,6 +565,9 @@ abstract class WC_REST_Terms_Controller extends WC_REST_Controller {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
}
// Schedule action to assign default category.
wc_get_container()->get( AssignDefaultCategory::class )->schedule_action();
/**
* Fires after a single term is deleted via the REST API.
*

View File

@ -6,6 +6,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -29,8 +31,7 @@ class WC_Twenty_Eleven {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 150,
'single_image_width' => 300,

View File

@ -7,6 +7,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -30,8 +32,7 @@ class WC_Twenty_Fifteen {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 200,
'single_image_width' => 350,

View File

@ -7,6 +7,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -30,8 +32,7 @@ class WC_Twenty_Fourteen {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 150,
'single_image_width' => 300,

View File

@ -7,6 +7,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
@ -37,8 +38,7 @@ class WC_Twenty_Nineteen {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 300,
'single_image_width' => 450,
@ -48,7 +48,7 @@ class WC_Twenty_Nineteen {
// Tweak Twenty Nineteen features.
add_action( 'wp', array( __CLASS__, 'tweak_theme_features' ) );
// Color scheme CSS
// Color scheme CSS.
add_filter( 'twentynineteen_custom_colors_css', array( __CLASS__, 'custom_colors_css' ), 10, 3 );
}

View File

@ -7,6 +7,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
@ -30,8 +31,7 @@ class WC_Twenty_Seventeen {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 250,
'single_image_width' => 350,

View File

@ -6,6 +6,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -29,8 +31,7 @@ class WC_Twenty_Sixteen {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 250,
'single_image_width' => 400,

View File

@ -6,6 +6,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -29,8 +31,7 @@ class WC_Twenty_Ten {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 200,
'single_image_width' => 300,

View File

@ -7,6 +7,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -30,8 +32,7 @@ class WC_Twenty_Thirteen {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 200,
'single_image_width' => 300,

View File

@ -7,6 +7,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
/**
@ -30,8 +32,7 @@ class WC_Twenty_Twelve {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 200,
'single_image_width' => 300,

View File

@ -7,6 +7,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
@ -37,8 +38,7 @@ class WC_Twenty_Twenty_One {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 450,
'single_image_width' => 600,

View File

@ -7,6 +7,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
@ -37,8 +38,7 @@ class WC_Twenty_Twenty {
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
add_theme_support(
'woocommerce',
wc_get_container()->get( ThemeSupport::class )->add_default_options(
array(
'thumbnail_image_width' => 450,
'single_image_width' => 600,

View File

@ -9,6 +9,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\ThemeSupport;
use Automattic\WooCommerce\Utilities\NumberUtil;
if ( ! defined( 'ABSPATH' ) ) {
@ -390,7 +391,7 @@ function wc_locate_template( $template_name, $template_path = '', $default_path
// Look within passed path within the theme - this is priority.
if ( false !== strpos( $template_name, 'product_cat' ) || false !== strpos( $template_name, 'product_tag' ) ) {
$cs_template = str_replace( '_', '-', $template_name );
$template = locate_template(
$template = locate_template(
array(
trailingslashit( $template_path ) . $cs_template,
$cs_template,
@ -879,38 +880,7 @@ function wc_mail( $to, $subject, $message, $headers = "Content-Type: text/html\r
* @return mixed Value of prop(s).
*/
function wc_get_theme_support( $prop = '', $default = null ) {
$theme_support = get_theme_support( 'woocommerce' );
$theme_support = is_array( $theme_support ) ? $theme_support[0] : false;
if ( ! $theme_support ) {
return $default;
}
if ( $prop ) {
$prop_stack = explode( '::', $prop );
$prop_key = array_shift( $prop_stack );
if ( isset( $theme_support[ $prop_key ] ) ) {
$value = $theme_support[ $prop_key ];
if ( count( $prop_stack ) ) {
foreach ( $prop_stack as $prop_key ) {
if ( is_array( $value ) && isset( $value[ $prop_key ] ) ) {
$value = $value[ $prop_key ];
} else {
$value = $default;
break;
}
}
}
} else {
$value = $default;
}
return $value;
}
return $theme_support;
return wc_get_container()->get( ThemeSupport::class )->get_option( $prop, $default );
}
/**
@ -1270,7 +1240,7 @@ function wc_format_country_state_string( $country_string ) {
* @return array
*/
function wc_get_base_location() {
$default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country' ) );
$default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country', 'US:CA' ) );
return wc_format_country_state_string( $default );
}
@ -1286,7 +1256,7 @@ function wc_get_base_location() {
*/
function wc_get_customer_default_location() {
$set_default_location_to = get_option( 'woocommerce_default_customer_address', 'base' );
$default_location = '' === $set_default_location_to ? '' : get_option( 'woocommerce_default_country', '' );
$default_location = '' === $set_default_location_to ? '' : get_option( 'woocommerce_default_country', 'US:CA' );
$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', $default_location ) );
// Geolocation takes priority if used and if geolocation is possible.
@ -2269,9 +2239,9 @@ function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
}
$new_version = wc_clean( $plugin->new_version );
$plugin_updates = new WC_Plugin_Updates();
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
$new_version = wc_clean( $plugin->new_version );
$plugin_updates = new WC_Plugin_Updates();
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
if ( ! is_string( $version_type ) ) {
$version_type = 'none';
}

View File

@ -403,7 +403,7 @@ function wc_get_low_stock_amount( WC_Product $product ) {
$low_stock_amount = $product->get_low_stock_amount();
if ( '' === $low_stock_amount && $product->is_type( 'variation' ) ) {
$product = wc_get_product( $product->get_parent_id() );
$product = wc_get_product( $product->get_parent_id() );
$low_stock_amount = $product->get_low_stock_amount();
}
@ -411,5 +411,5 @@ function wc_get_low_stock_amount( WC_Product $product ) {
$low_stock_amount = get_option( 'woocommerce_notify_low_stock_amount', 2 );
}
return $low_stock_amount;
return (int) $low_stock_amount;
}

View File

@ -2753,8 +2753,9 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
$field .= '<input type="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="' . current( array_keys( $countries ) ) . '" ' . implode( ' ', $custom_attributes ) . ' class="country_to_state" readonly="readonly" />';
} else {
$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region&hellip;', 'woocommerce' ) ) . '"><option value="">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>';
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region&hellip;', 'woocommerce' ) ) . '" ' . $data_label . '><option value="">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>';
foreach ( $countries as $ckey => $cvalue ) {
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>';
@ -2779,8 +2780,9 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
$field .= '<input type="hidden" class="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="" ' . implode( ' ', $custom_attributes ) . ' placeholder="' . esc_attr( $args['placeholder'] ) . '" readonly="readonly" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '"/>';
} elseif ( ! is_null( $for_country ) && is_array( $states ) ) {
$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';
$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="state_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option&hellip;', 'woocommerce' ) ) . '" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '">
$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="state_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option&hellip;', 'woocommerce' ) ) . '" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . $data_label . '>
<option value="">' . esc_html__( 'Select an option&hellip;', 'woocommerce' ) . '</option>';
foreach ( $states as $ckey => $cvalue ) {

View File

@ -486,7 +486,8 @@ function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_
$term_query['join'] .= " INNER JOIN ( SELECT object_id FROM {$wpdb->term_relationships} INNER JOIN {$wpdb->term_taxonomy} using( term_taxonomy_id ) WHERE term_id IN ( " . implode( ',', array_map( 'absint', $terms_to_count ) ) . ' ) ) AS include_join ON include_join.object_id = p.ID';
// Get the count.
$count = $wpdb->get_var( implode( ' ', $term_query ) ); // WPCS: unprepared SQL ok.
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( implode( ' ', $term_query ) );
// Update the count.
update_term_meta( $term_id, 'product_count_' . $taxonomy->name, absint( $count ) );

View File

@ -10,6 +10,8 @@
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
/**
* Update file paths for 2.0
*
@ -1567,31 +1569,12 @@ function wc_update_330_webhooks() {
* Assign default cat to all products with no cats.
*/
function wc_update_330_set_default_product_cat() {
global $wpdb;
$default_category = get_option( 'default_product_cat', 0 );
if ( $default_category ) {
$wpdb->query(
$wpdb->prepare(
"INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id)
SELECT DISTINCT posts.ID, %s FROM {$wpdb->posts} posts
LEFT JOIN
(
SELECT object_id FROM {$wpdb->term_relationships} term_relationships
LEFT JOIN {$wpdb->term_taxonomy} term_taxonomy ON term_relationships.term_taxonomy_id = term_taxonomy.term_taxonomy_id
WHERE term_taxonomy.taxonomy = 'product_cat'
) AS tax_query
ON posts.ID = tax_query.object_id
WHERE posts.post_type = 'product'
AND tax_query.object_id IS NULL",
$default_category
)
);
wp_cache_flush();
delete_transient( 'wc_term_counts' );
wp_update_term_count_now( array( $default_category ), 'product_cat' );
}
/*
* When a product category is deleted, we need to check
* if the product has no categories assigned. Then assign
* it a default category.
*/
wc_get_container()->get( AssignDefaultCategory::class )->maybe_assign_default_product_cat();
}
/**

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "woocommerce",
"version": "5.1.0",
"version": "5.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -7,12 +7,14 @@ namespace Automattic\WooCommerce;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\AssignDefaultCategoryServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ThemeManagementServiceProvider;
/**
* PSR11 compliant dependency injection container for WooCommerce.
*
* Classes in the `src` directory should specify dependencies from that directory via constructor arguments
* Classes in the `src` directory should specify dependencies from that directory via an 'init' method having arguments
* with type hints. If an instance of the container itself is needed, the type hint to use is \Psr\Container\ContainerInterface.
*
* Classes in the `src` directory should interact with anything outside (especially code in the `includes` directory
@ -34,7 +36,9 @@ final class Container implements \Psr\Container\ContainerInterface {
*/
private $service_providers = array(
ProxiesServiceProvider::class,
ThemeManagementServiceProvider::class,
DownloadPermissionsAdjusterServiceProvider::class,
AssignDefaultCategoryServiceProvider::class,
);
/**

View File

@ -0,0 +1,73 @@
<?php
/**
* AssignDefaultCategory class file.
*/
namespace Automattic\WooCommerce\Internal;
defined( 'ABSPATH' ) || exit;
/**
* Class to assign default category to products.
*/
class AssignDefaultCategory {
/**
* Class initialization, to be executed when the class is resolved by the container.
*
* @internal
*/
final public function init() {
add_action( 'wc_schedule_update_product_default_cat', array( $this, 'maybe_assign_default_product_cat' ) );
}
/**
* When a product category is deleted, we need to check
* if the product has no categories assigned. Then assign
* it a default category. We delay this with a scheduled
* action job to not block the response.
*
* @return void
*/
public function schedule_action() {
WC()->queue()->schedule_single(
time(),
'wc_schedule_update_product_default_cat',
array(),
'wc_update_product_default_cat'
);
}
/**
* Assigns default product category for products
* that have no categories.
*
* @return void
*/
public function maybe_assign_default_product_cat() {
global $wpdb;
$default_category = get_option( 'default_product_cat', 0 );
if ( $default_category ) {
$wpdb->query(
$wpdb->prepare(
"INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id)
SELECT DISTINCT posts.ID, %s FROM {$wpdb->posts} posts
LEFT JOIN
(
SELECT object_id FROM {$wpdb->term_relationships} term_relationships
LEFT JOIN {$wpdb->term_taxonomy} term_taxonomy ON term_relationships.term_taxonomy_id = term_taxonomy.term_taxonomy_id
WHERE term_taxonomy.taxonomy = 'product_cat'
) AS tax_query
ON posts.ID = tax_query.object_id
WHERE posts.post_type = 'product'
AND tax_query.object_id IS NULL",
$default_category
)
);
wp_cache_flush();
delete_transient( 'wc_term_counts' );
wp_update_term_count_now( array( $default_category ), 'product_cat' );
}
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* AssignDefaultCategoryServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
/**
* Service provider for the AssignDefaultCategory class.
*/
class AssignDefaultCategoryServiceProvider extends AbstractServiceProvider {
/**
* The classes/interfaces that are serviced by this service provider.
*
* @var array
*/
protected $provides = array(
AssignDefaultCategory::class,
);
/**
* Register the classes.
*/
public function register() {
$this->share( AssignDefaultCategory::class );
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* ThemeManagementServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
use Automattic\WooCommerce\Internal\ThemeSupport;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
/**
* Service provider for the classes in the Automattic\WooCommerce\ThemeManagement namespace.
*/
class ThemeManagementServiceProvider extends AbstractServiceProvider {
/**
* The classes/interfaces that are serviced by this service provider.
*
* @var array
*/
protected $provides = array(
ThemeSupport::class,
);
/**
* Register the classes.
*/
public function register() {
$this->share_with_auto_arguments( ThemeSupport::class );
}
}

View File

@ -0,0 +1,118 @@
<?php
/**
* ThemeSupport class file.
*
* @package Automattic\WooCommerce\ThemeManagement
*/
namespace Automattic\WooCommerce\Internal;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\ArrayUtil;
/**
* Provides methods for theme support.
*/
class ThemeSupport {
const DEFAULTS_KEY = '_defaults';
/**
* The instance of LegacyProxy to use.
*
* @var LegacyProxy
*/
private $legacy_proxy;
/**
* ThemeSupport constructor.
*
* @internal
* @param LegacyProxy $legacy_proxy The instance of LegacyProxy to use.
*/
final public function init( LegacyProxy $legacy_proxy ) {
$this->legacy_proxy = $legacy_proxy;
}
/**
* Adds theme support options for the current theme.
*
* @param array $options The options to be added.
*/
public function add_options( $options ) {
$this->legacy_proxy->call_function( 'add_theme_support', 'woocommerce', $options );
}
/**
* Adds default theme support options for the current theme.
*
* @param array $options The options to be added.
*/
public function add_default_options( $options ) {
$default_options = $this->get_option( self::DEFAULTS_KEY, array() );
$default_options = array_merge( $default_options, $options );
$this->add_options( array( self::DEFAULTS_KEY => $default_options ) );
}
/**
* Gets "theme support" options from the current theme, if set.
*
* @param string $option_name Option name, possibly nested (key::subkey), to get specific value. Blank to get all the existing options as an array.
* @param mixed $default_value Value to return if the specified option doesn't exist.
* @return mixed The retrieved option or the default value.
*/
public function get_option( $option_name = '', $default_value = null ) {
$theme_support_options = $this->get_all_options();
if ( ! $theme_support_options ) {
return $default_value;
}
if ( $option_name ) {
$value = ArrayUtil::get_nested_value( $theme_support_options, $option_name );
if ( is_null( $value ) ) {
$value = ArrayUtil::get_nested_value( $theme_support_options, self::DEFAULTS_KEY . '::' . $option_name, $default_value );
}
return $value;
}
return $theme_support_options;
}
/**
* Checks whether a given theme support option has been defined.
*
* @param string $option_name The (possibly nested) name of the option to check.
* @param bool $include_defaults True to include the default values in the check, false otherwise.
*
* @return bool True if the specified theme support option has been defined, false otherwise.
*/
public function has_option( $option_name, $include_defaults = true ) {
$theme_support_options = $this->get_all_options();
if ( ! $theme_support_options ) {
return false;
}
$value = ArrayUtil::get_nested_value( $theme_support_options, $option_name );
if ( ! is_null( $value ) ) {
return true;
}
if ( ! $include_defaults ) {
return false;
}
$value = ArrayUtil::get_nested_value( $theme_support_options, self::DEFAULTS_KEY . '::' . $option_name );
return ! is_null( $value );
}
/**
* Get all the defined theme support options for the 'woocommerce' feature.
*
* @return array An array with all the theme support options defined for the 'woocommerce' feature, or false if nothing has been defined for that feature.
*/
private function get_all_options() {
$theme_support = $this->legacy_proxy->call_function( 'get_theme_support', 'woocommerce' );
return is_array( $theme_support ) ? $theme_support[0] : false;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* A class of utilities for dealing with arrays.
*/
namespace Automattic\WooCommerce\Utilities;
/**
* A class of utilities for dealing with arrays.
*/
class ArrayUtil {
/**
* Get a value from an nested array by specifying the entire key hierarchy with '::' as separator.
*
* E.g. for [ 'foo' => [ 'bar' => [ 'fizz' => 'buzz' ] ] ] the value for key 'foo::bar::fizz' would be 'buzz'.
*
* @param array $array The array to get the value from.
* @param string $key The complete key hierarchy, using '::' as separator.
* @param mixed $default The value to return if the key doesn't exist in the array.
*
* @return mixed The retrieved value, or the supplied default value.
* @throws \Exception $array is not an array.
*/
public static function get_nested_value( array $array, string $key, $default = null ) {
$key_stack = explode( '::', $key );
$subkey = array_shift( $key_stack );
if ( isset( $array[ $subkey ] ) ) {
$value = $array[ $subkey ];
if ( count( $key_stack ) ) {
foreach ( $key_stack as $subkey ) {
if ( is_array( $value ) && isset( $value[ $subkey ] ) ) {
$value = $value[ $subkey ];
} else {
$value = $default;
break;
}
}
}
} else {
$value = $default;
}
return $value;
}
}

View File

@ -15,6 +15,7 @@
- Shopper Checkout Login Account
- Shopper My Account Create Account
- Shopper Cart Calculate Shipping
- Shopper Cart Redirection
## Fixed

View File

@ -85,6 +85,7 @@ The functions to access the core tests are:
- `runCheckoutCreateAccountTest` - Shopper can create an account during checkout
- `runCheckoutLoginAccountTest` - Shopper can login to an account during checkout
- `runMyAccountCreateAccountTest` - Shopper can create an account via my account page
- `runCartCalculateShippingTest` - Shopper can calculate shipping in the cart
- `runCartRedirectionTest` - Shopper is redirected to the cart page after adding to cart
- `runOrderEmailReceivingTest` - Shopper can receive an email for his order

View File

@ -21,6 +21,7 @@ const runSingleProductPageTest = require( './shopper/front-end-single-product.te
const runVariableProductUpdateTest = require( './shopper/front-end-variable-product-updates.test' );
const runCheckoutCreateAccountTest = require( './shopper/front-end-checkout-create-account.test' );
const runCheckoutLoginAccountTest = require( './shopper/front-end-checkout-login-account.test' );
const runCartCalculateShippingTest = require( './shopper/front-end-cart-calculate-shipping.test' );
const runCartRedirectionTest = require( './shopper/front-end-cart-redirection.test' );
const runOrderEmailReceivingTest = require( './shopper/front-end-order-email-receiving.test' );
@ -71,7 +72,8 @@ const runShopperTests = () => {
runVariableProductUpdateTest();
runCheckoutCreateAccountTest();
runCheckoutLoginAccountTest();
runCartRedirectionTest();
runCartCalculateShippingTest();
runCartRedirectionTest();
runOrderEmailReceivingTest();
};
@ -148,6 +150,7 @@ module.exports = {
runCheckoutCreateAccountTest,
runImportProductsTest,
runCheckoutLoginAccountTest,
runCartCalculateShippingTest,
runCartRedirectionTest,
runMyAccountCreateAccountTest,
runOrderEmailReceivingTest,

View File

@ -10,10 +10,18 @@ const {
addShippingZoneAndMethod,
clearAndFillInput,
selectOptionInSelect2,
evalAndClick,
uiUnblocked,
deleteAllShippingZones,
} = require( '@woocommerce/e2e-utils' );
/**
* External dependencies
*/
const {
it,
describe,
beforeAll,
} = require( '@jest/globals' );
const config = require( 'config' );
const simpleProductPrice = config.has( 'products.simple.price' ) ? config.get( 'products.simple.price' ) : '9.99';
const simpleProductName = config.get( 'products.simple.name' );
@ -28,27 +36,7 @@ const runAddNewShippingZoneTest = () => {
beforeAll(async () => {
await merchant.login();
await createSimpleProduct();
await merchant.openSettings('shipping');
// Delete existing shipping zones.
try {
let zone = await page.$( '.wc-shipping-zone-delete' );
if ( zone ) {
// WP action links aren't clickable because they are hidden with a left=-9999 style.
await page.evaluate(() => {
document.querySelector('.wc-shipping-zone-name .row-actions')
.style
.left = '0';
});
while ( zone ) {
await evalAndClick( '.wc-shipping-zone-delete' );
await uiUnblocked();
zone = await page.$( '.wc-shipping-zone-delete' );
}
}
} catch (error) {
// Prevent an error here causing the test to fail.
}
await deleteAllShippingZones();
});
it('add shipping zone for San Francisco with free Local pickup', async () => {

View File

@ -0,0 +1,136 @@
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
/**
* Internal dependencies
*/
const {
shopper,
merchant,
createSimpleProduct,
addShippingZoneAndMethod,
clearAndFillInput,
uiUnblocked,
selectOptionInSelect2,
} = require( '@woocommerce/e2e-utils' );
/**
* External dependencies
*/
const {
it,
describe,
beforeAll,
} = require( '@jest/globals' );
const config = require( 'config' );
const firstProductPrice = config.has( 'products.simple.price' ) ? config.get( 'products.simple.price' ) : '9.99';
const secondProductPrice = '4.99';
const fourProductPrice = firstProductPrice * 4;
var twoProductsPrice = (+firstProductPrice) + (+secondProductPrice);
var firstProductPriceWithFlatRate = (+firstProductPrice) + (+5);
var fourProductPriceWithFlatRate = (+fourProductPrice) + (+5);
var twoProductsPriceWithFlatRate = (+twoProductsPrice) + (+5);
const firstProductName = 'First Product';
const secondProductName = 'Second Product';
const shippingZoneNameDE = 'Germany Free Shipping';
const shippingCountryDE = 'country:DE';
const shippingZoneNameFR = 'France Flat Local';
const shippingCountryFR = 'country:FR';
const runCartCalculateShippingTest = () => {
describe('Cart Calculate Shipping', () => {
beforeAll(async () => {
await merchant.login();
await createSimpleProduct(firstProductName);
await createSimpleProduct(secondProductName, secondProductPrice);
await merchant.openNewShipping();
// Add a new shipping zone Germany with Free shipping
await addShippingZoneAndMethod(shippingZoneNameDE, shippingCountryDE, ' ', 'free_shipping');
// Add a new shipping zone for France with Flat rate & Local pickup
await addShippingZoneAndMethod(shippingZoneNameFR, shippingCountryFR, ' ', 'flat_rate');
await page.waitFor(1000); // to avoid flakiness in headless
await page.click('a.wc-shipping-zone-method-settings', {text: 'Flat rate'});
await clearAndFillInput('#woocommerce_flat_rate_cost', '5');
await page.click('.wc-backbone-modal-main button#btn-ok');
// Add additional method Local pickup for the same location
await page.waitFor(1000); // to avoid flakiness in headless
await page.click('button.wc-shipping-zone-add-method', {text:'Add shipping method'});
await page.waitForSelector('.wc-shipping-zone-method-selector');
await page.select('select[name="add_method_id"]', 'local_pickup');
await page.click('button#btn-ok');
await page.waitForSelector('#zone_locations');
await merchant.logout();
await shopper.emptyCart();
});
it('allows customer to calculate Free Shipping if in Germany', async () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage(firstProductName);
await shopper.goToCart();
// Set shipping country to Germany
await expect(page).toClick('a.shipping-calculator-button');
await expect(page).toClick('#select2-calc_shipping_country-container');
await selectOptionInSelect2('Germany');
await expect(page).toClick('button[name="calc_shipping"]');
// Verify shipping costs
await page.waitForSelector('.order-total');
await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Free shipping'});
await expect(page).toMatchElement('.order-total .amount', {text: `$${firstProductPrice}`});
});
it('allows customer to calculate Flat rate and Local pickup if in France', async () => {
await page.reload();
// Set shipping country to France
await expect(page).toClick('a.shipping-calculator-button');
await expect(page).toClick('#select2-calc_shipping_country-container');
await selectOptionInSelect2('France');
await expect(page).toClick('button[name="calc_shipping"]');
// Verify shipping costs
await page.waitForSelector('.order-total');
await expect(page).toMatchElement('.shipping .amount', {text: '$5.00'});
await expect(page).toMatchElement('.order-total .amount', {text: `$${firstProductPriceWithFlatRate}`});
});
it('should show correct total cart price after updating quantity', async () => {
await shopper.setCartQuantity(firstProductName, 4);
await expect(page).toClick('button', {text: 'Update cart'});
await uiUnblocked();
await expect(page).toMatchElement('.order-total .amount', {text: `$${fourProductPriceWithFlatRate}`});
});
it('should show correct total cart price with 2 products and flat rate', async () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage(secondProductName);
await shopper.goToCart();
await shopper.setCartQuantity(firstProductName, 1);
await expect(page).toClick('button', {text: 'Update cart'});
await uiUnblocked();
await page.waitForSelector('.order-total');
await expect(page).toMatchElement('.shipping .amount', {text: '$5.00'});
await expect(page).toMatchElement('.order-total .amount', {text: `$${twoProductsPriceWithFlatRate}`});
});
it('should show correct total cart price with 2 products without flat rate', async () => {
await page.reload();
// Set shipping country to Spain
await expect(page).toClick('a.shipping-calculator-button');
await expect(page).toClick('#select2-calc_shipping_country-container');
await selectOptionInSelect2('Spain');
await expect(page).toClick('button[name="calc_shipping"]');
// Verify shipping costs
await page.waitForSelector('.order-total');
await expect(page).toMatchElement('.order-total .amount', {text: `$${twoProductsPrice}`});
});
});
};
module.exports = runCartCalculateShippingTest;

View File

@ -0,0 +1,6 @@
/*
* Internal dependencies
*/
const { runCartCalculateShippingTest } = require( '@woocommerce/e2e-core-tests' );
runCartCalculateShippingTest();

View File

@ -3,6 +3,7 @@
## Added
- `emptyCart()` Shopper flow helper that empties the cart
- `deleteAllShippingZones` Delete all the existing shipping zones
- constants
- `WP_ADMIN_POST_TYPE`
- `WP_ADMIN_NEW_POST_TYPE`

View File

@ -180,6 +180,7 @@ This package provides support for enabling retries in tests:
| `removeCoupon` | | helper method that removes a single coupon within cart or checkout |
| `selectOrderAction` | `action` | Helper method to select an order action in the `Order Actions` postbox |
| `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page |
| `deleteAllShippingZones` | | Delete all the existing shipping zones |
### Test Utilities

View File

@ -13,6 +13,7 @@ import {
selectOptionInSelect2,
setCheckbox,
unsetCheckbox,
evalAndClick,
clearAndFillInput,
} from './page-utils';
import factories from './factories';
@ -540,6 +541,33 @@ const deleteAllEmailLogs = async () => {
}
};
/**
* Delete all the existing shipping zones.
*/
const deleteAllShippingZones = async () => {
await merchant.openSettings('shipping');
// Delete existing shipping zones.
try {
let zone = await page.$( '.wc-shipping-zone-delete' );
if ( zone ) {
// WP action links aren't clickable because they are hidden with a left=-9999 style.
await page.evaluate(() => {
document.querySelector('.wc-shipping-zone-name .row-actions')
.style
.left = '0';
});
while ( zone ) {
await evalAndClick( '.wc-shipping-zone-delete' );
await uiUnblocked();
zone = await page.$( '.wc-shipping-zone-delete' );
};
};
} catch (error) {
// Prevent an error here causing the test to fail.
};
};
export {
completeOnboardingWizard,
createSimpleProduct,
@ -553,4 +581,5 @@ export {
createSimpleProductWithCategory,
clickUpdateOrder,
deleteAllEmailLogs,
deleteAllShippingZones,
};

View File

@ -236,6 +236,10 @@ export const searchForOrder = async (value, orderId, customerName) => {
*/
export const applyCoupon = async ( couponCode ) => {
try {
await Promise.all([
page.reload(),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
]);
await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked();
await clearAndFillInput('#coupon_code', couponCode);
@ -255,6 +259,10 @@ export const applyCoupon = async ( couponCode ) => {
* @returns {Promise<void>}
*/
export const removeCoupon = async ( couponCode ) => {
await Promise.all([
page.reload(),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
]);
await expect(page).toClick('[data-coupon="'+couponCode.toLowerCase()+'"]', {text: '[Remove]'});
await uiUnblocked();
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});

View File

@ -103,7 +103,14 @@ class WC_Unit_Tests_Bootstrap {
* @throws Exception Error when initializing one of the hacks.
*/
private function initialize_code_hacker() {
CodeHacker::initialize( array( __DIR__ . '/../../includes/' ) );
$wp_dir = getenv( 'WP_TESTS_WP_DIR' ) ? getenv( 'WP_TESTS_WP_DIR' ) : sys_get_temp_dir() . '/wordpress';
CodeHacker::initialize(
array(
$this->plugin_dir . '/includes/',
$wp_dir . '/wp-includes/class-wp-customize-manager.php',
)
);
$replaceable_functions = include_once __DIR__ . '/mockable-functions.php';
if ( ! empty( $replaceable_functions ) ) {
FunctionsMockerHack::initialize( $replaceable_functions );

View File

@ -7,6 +7,7 @@
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker;
use PHPUnit\Framework\Constraint\IsType;
/**
* WC Unit Test Case.
@ -246,4 +247,16 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
public function register_legacy_proxy_class_mocks( array $mocks ) {
wc_get_container()->get( LegacyProxy::class )->register_class_mocks( $mocks );
}
/**
* Asserts that a variable is of type int.
* TODO: After upgrading to PHPUnit 8 or newer, remove this method and replace calls with PHPUnit's built-in 'assertIsInt'.
*
* @param mixed $actual The value to check.
* @param mixed $message Error message to use if the assertion fails.
* @return bool mixed True if the value is of integer type, false otherwise.
*/
public static function assertIsInteger( $actual, $message = '' ) {
return self::assertInternalType( 'int', $actual, $message );
}
}

View File

@ -17,15 +17,15 @@ class WC_Helper_Customer {
'id' => 0,
'date_modified' => null,
'country' => 'US',
'state' => 'PA',
'postcode' => '19123',
'city' => 'Philadelphia',
'state' => 'CA',
'postcode' => '94110',
'city' => 'San Francisco',
'address' => '123 South Street',
'address_2' => 'Apt 1',
'shipping_country' => 'US',
'shipping_state' => 'PA',
'shipping_postcode' => '19123',
'shipping_city' => 'Philadelphia',
'shipping_state' => 'CA',
'shipping_postcode' => '94110',
'shipping_city' => 'San Francisco',
'shipping_address' => '123 South Street',
'shipping_address_2' => 'Apt 1',
'is_vat_exempt' => false,
@ -46,15 +46,15 @@ class WC_Helper_Customer {
$customer = new WC_Customer();
$customer->set_billing_country( 'US' );
$customer->set_first_name( 'Justin' );
$customer->set_billing_state( 'PA' );
$customer->set_billing_postcode( '19123' );
$customer->set_billing_city( 'Philadelphia' );
$customer->set_billing_state( 'CA' );
$customer->set_billing_postcode( '94110' );
$customer->set_billing_city( 'San Francisco' );
$customer->set_billing_address( '123 South Street' );
$customer->set_billing_address_2( 'Apt 1' );
$customer->set_shipping_country( 'US' );
$customer->set_shipping_state( 'PA' );
$customer->set_shipping_postcode( '19123' );
$customer->set_shipping_city( 'Philadelphia' );
$customer->set_shipping_state( 'CA' );
$customer->set_shipping_postcode( '94110' );
$customer->set_shipping_city( 'San Francisco' );
$customer->set_shipping_address( '123 South Street' );
$customer->set_shipping_address_2( 'Apt 1' );
$customer->set_username( $username );
@ -70,7 +70,7 @@ class WC_Helper_Customer {
* @return array
*/
public static function get_expected_store_location() {
return array( 'GB', '', '', '' );
return array( 'US', 'CA', '', '' );
}
/**

View File

@ -179,7 +179,7 @@ class WC_Tests_Account_Functions extends WC_Unit_Test_Case {
public function test_wc_get_account_formatted_address() {
$customer = WC_Helper_Customer::create_customer();
$this->assertEquals( '123 South Street<br/>Apt 1<br/>Philadelphia, PA 19123<br/>United States (US)', wc_get_account_formatted_address( 'billing', $customer->get_id() ) );
$this->assertEquals( '123 South Street<br/>Apt 1<br/>San Francisco, CA 94110', wc_get_account_formatted_address( 'billing', $customer->get_id() ) );
$customer->delete( true );
}

View File

@ -5,6 +5,8 @@
* @package WooCommerce\Tests\Countries
*/
// phpcs:disable WordPress.Files.FileName
/**
* WC_Countries tests.
*/
@ -178,7 +180,7 @@ class WC_Tests_Countries extends WC_Unit_Test_Case {
update_option( 'woocommerce_default_country', 'NO' );
$this->assertEquals( 'VAT', $countries->tax_or_vat() );
update_option( 'woocommerce_default_country', 'US' );
update_option( 'woocommerce_default_country', 'US:CA' );
$this->assertEquals( 'Tax', $countries->tax_or_vat() );
}

View File

@ -324,16 +324,16 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case {
update_option( 'woocommerce_tax_based_on', 'shipping' );
$taxable = $customer->get_taxable_address();
$this->assertEquals( 'US', $taxable[0] );
$this->assertEquals( 'PA', $taxable[1] );
$this->assertEquals( 'CA', $taxable[1] );
$this->assertEquals( '11111', $taxable[2] );
$this->assertEquals( 'Test', $taxable[3] );
update_option( 'woocommerce_tax_based_on', 'billing' );
$taxable = $customer->get_taxable_address();
$this->assertEquals( 'US', $taxable[0] );
$this->assertEquals( 'PA', $taxable[1] );
$this->assertEquals( '19123', $taxable[2] );
$this->assertEquals( 'Philadelphia', $taxable[3] );
$this->assertEquals( 'CA', $taxable[1] );
$this->assertEquals( '94110', $taxable[2] );
$this->assertEquals( 'San Francisco', $taxable[3] );
update_option( 'woocommerce_tax_based_on', 'base' );
$taxable = $customer->get_taxable_address();
@ -431,7 +431,7 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case {
*/
public function test_customer_is_customer_outside_base() {
$customer = WC_Helper_Customer::create_customer();
$this->assertTrue( $customer->is_customer_outside_base() );
$this->assertFalse( $customer->is_customer_outside_base() );
update_option( 'woocommerce_tax_based_on', 'base' );
$customer->set_billing_address_to_base();
$this->assertFalse( $customer->is_customer_outside_base() );
@ -444,9 +444,9 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case {
public function test_customer_sessions() {
$session = WC_Helper_Customer::create_mock_customer(); // set into session....
$this->assertEquals( '19123', $session->get_billing_postcode() );
$this->assertEquals( '94110', $session->get_billing_postcode() );
$this->assertEquals( '123 South Street', $session->get_billing_address() );
$this->assertEquals( 'Philadelphia', $session->get_billing_city() );
$this->assertEquals( 'San Francisco', $session->get_billing_city() );
$session->set_billing_address( '124 South Street' );
$session->save();

View File

@ -83,7 +83,7 @@ class WC_Tests_Customer extends WC_Unit_Test_Case {
// Customer is going with the Free Shipping option, and the store calculates tax based on the customer's billing address.
WC_Helper_Customer::set_chosen_shipping_methods( array( 'free_shipping' ) );
WC_Helper_Customer::set_tax_based_on( 'billing' );
$this->assertEquals( $customer->is_customer_outside_base(), true );
$this->assertEquals( $customer->is_customer_outside_base(), false );
// Customer is going with the Free Shipping option, and the store calculates tax based on the store base location.
WC_Helper_Customer::set_chosen_shipping_methods( array( 'free_shipping' ) );

View File

@ -593,32 +593,32 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_price() {
// Common prices.
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1.00</bdi></span>', wc_price( 1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1.10</bdi></span>', wc_price( 1.1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1.17</bdi></span>', wc_price( 1.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1,111.17</bdi></span>', wc_price( 1111.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( 0 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1.00</bdi></span>', wc_price( 1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1.10</bdi></span>', wc_price( 1.1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1.17</bdi></span>', wc_price( 1.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1,111.17</bdi></span>', wc_price( 1111.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>0.00</bdi></span>', wc_price( 0 ) );
// Different currency.
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1,111.17</bdi></span>', wc_price( 1111.17, array( 'currency' => 'USD' ) ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1,111.17</bdi></span>', wc_price( 1111.17, array( 'currency' => 'GBP' ) ) );
// Negative price.
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi>-<span class="woocommerce-Price-currencySymbol">&pound;</span>1.17</bdi></span>', wc_price( -1.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi>-<span class="woocommerce-Price-currencySymbol">&#36;</span>1.17</bdi></span>', wc_price( -1.17 ) );
// Bogus prices.
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( null ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( 'Q' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( 'ಠ_ಠ' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>0.00</bdi></span>', wc_price( null ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>0.00</bdi></span>', wc_price( 'Q' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>0.00</bdi></span>', wc_price( 'ಠ_ಠ' ) );
// Trim zeros.
add_filter( 'woocommerce_price_trim_zeros', '__return_true' );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1</bdi></span>', wc_price( 1.00 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1</bdi></span>', wc_price( 1.00 ) );
remove_filter( 'woocommerce_price_trim_zeros', '__return_true' );
// Ex tax label.
$calc_taxes = get_option( 'woocommerce_calc_taxes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1,111.17</bdi></span> <small class="woocommerce-Price-taxLabel tax_label">(ex. VAT)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1,111.17</bdi></span> <small class="woocommerce-Price-taxLabel tax_label">(ex. tax)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
update_option( 'woocommerce_calc_taxes', $calc_taxes );
}
@ -926,7 +926,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_sale_price() {
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>5.00</bdi></span></ins>', wc_format_sale_price( '10', '5' ) );
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>5.00</bdi></span></ins>', wc_format_sale_price( '10', '5' ) );
}
/**
@ -935,7 +935,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_price_range() {
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</bdi></span> &ndash; <span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>5.00</bdi></span>', wc_format_price_range( '10', '5' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>10.00</bdi></span> &ndash; <span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>5.00</bdi></span>', wc_format_price_range( '10', '5' ) );
}
/**

View File

@ -1245,7 +1245,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$order->set_billing_country( 'US' );
$order->set_billing_city( 'Portland' );
$order->set_billing_postcode( '97266' );
$this->assertEquals( '123 Test St.<br/>Portland, 97266<br/>United States (US)', $order->get_formatted_billing_address( 'none' ) );
$this->assertEquals( '123 Test St.<br/>Portland, 97266', $order->get_formatted_billing_address( 'none' ) );
$this->assertTrue( $order->has_billing_address() );
$this->assertFalse( $order->has_shipping_address() );
@ -1265,7 +1265,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$order->set_shipping_country( 'US' );
$order->set_shipping_city( 'Portland' );
$order->set_shipping_postcode( '97266' );
$this->assertEquals( '123 Test St.<br/>Portland, 97266<br/>United States (US)', $order->get_formatted_shipping_address( 'none' ) );
$this->assertEquals( '123 Test St.<br/>Portland, 97266', $order->get_formatted_shipping_address( 'none' ) );
$this->assertFalse( $order->has_billing_address() );
$this->assertTrue( $order->has_shipping_address() );
@ -1498,7 +1498,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object->set_billing_state( 'Boulder' );
$object->set_billing_postcode( '00001' );
$object->set_billing_country( 'US' );
$this->assertEquals( 'Fred Flintstone<br/>Bedrock Ltd.<br/>34 Stonepants avenue<br/>Rockville<br/>Bedrock, BOULDER 00001<br/>United States (US)', $object->get_formatted_billing_address() );
$this->assertEquals( 'Fred Flintstone<br/>Bedrock Ltd.<br/>34 Stonepants avenue<br/>Rockville<br/>Bedrock, BOULDER 00001', $object->get_formatted_billing_address() );
}
/**
@ -1515,7 +1515,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object->set_shipping_state( 'Boulder' );
$object->set_shipping_postcode( '00001' );
$object->set_shipping_country( 'US' );
$this->assertEquals( 'Barney Rubble<br/>Bedrock Ltd.<br/>34 Stonepants avenue<br/>Rockville<br/>Bedrock, BOULDER 00001<br/>United States (US)', $object->get_formatted_shipping_address() );
$this->assertEquals( 'Barney Rubble<br/>Bedrock Ltd.<br/>34 Stonepants avenue<br/>Rockville<br/>Bedrock, BOULDER 00001', $object->get_formatted_shipping_address() );
}
/**

View File

@ -86,15 +86,15 @@ class WC_Test_Privacy_Export extends WC_Unit_Test_Case {
),
array(
'name' => 'Billing City',
'value' => 'Philadelphia',
'value' => 'San Francisco',
),
array(
'name' => 'Billing Postal/Zip Code',
'value' => '19123',
'value' => '94110',
),
array(
'name' => 'Billing State',
'value' => 'PA',
'value' => 'CA',
),
array(
'name' => 'Billing Country / Region',
@ -114,15 +114,15 @@ class WC_Test_Privacy_Export extends WC_Unit_Test_Case {
),
array(
'name' => 'Shipping City',
'value' => 'Philadelphia',
'value' => 'San Francisco',
),
array(
'name' => 'Shipping Postal/Zip Code',
'value' => '19123',
'value' => '94110',
),
array(
'name' => 'Shipping State',
'value' => 'PA',
'value' => 'CA',
),
array(
'name' => 'Shipping Country / Region',

View File

@ -259,15 +259,15 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$product = wc_get_product( $product1_id );
$this->assertEquals( $product1_id, $product->get_id() );
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>7.00</bdi></span></ins>', $product->get_price_html() );
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>7.00</bdi></span></ins>', $product->get_price_html() );
$product = wc_get_product( $product2_id );
$this->assertEquals( $product2_id, $product->get_id() );
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>20.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>16.00</bdi></span></ins>', $product->get_price_html() );
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>20.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>16.00</bdi></span></ins>', $product->get_price_html() );
$product = wc_get_product( $product3_id );
$this->assertEquals( $product3_id, $product->get_id() );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>50.00</bdi></span>', $product->get_price_html() );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>50.00</bdi></span>', $product->get_price_html() );
}
/**

View File

@ -26,15 +26,15 @@ class CustomerHelper {
'id' => 0,
'date_modified' => null,
'country' => 'US',
'state' => 'PA',
'postcode' => '19123',
'city' => 'Philadelphia',
'state' => 'CA',
'postcode' => '94110',
'city' => 'San Francisco',
'address' => '123 South Street',
'address_2' => 'Apt 1',
'shipping_country' => 'US',
'shipping_state' => 'PA',
'shipping_postcode' => '19123',
'shipping_city' => 'Philadelphia',
'shipping_state' => 'CA',
'shipping_postcode' => '94110',
'shipping_city' => 'San Francisco',
'shipping_address' => '123 South Street',
'shipping_address_2' => 'Apt 1',
'is_vat_exempt' => false,
@ -55,15 +55,15 @@ class CustomerHelper {
$customer = new WC_Customer();
$customer->set_billing_country( 'US' );
$customer->set_first_name( 'Justin' );
$customer->set_billing_state( 'PA' );
$customer->set_billing_postcode( '19123' );
$customer->set_billing_city( 'Philadelphia' );
$customer->set_billing_state( 'CA' );
$customer->set_billing_postcode( '94110' );
$customer->set_billing_city( 'San Francisco' );
$customer->set_billing_address( '123 South Street' );
$customer->set_billing_address_2( 'Apt 1' );
$customer->set_shipping_country( 'US' );
$customer->set_shipping_state( 'PA' );
$customer->set_shipping_postcode( '19123' );
$customer->set_shipping_city( 'Philadelphia' );
$customer->set_shipping_state( 'CA' );
$customer->set_shipping_postcode( '94110' );
$customer->set_shipping_city( 'San Francisco' );
$customer->set_shipping_address( '123 South Street' );
$customer->set_shipping_address_2( 'Apt 1' );
$customer->set_username( $username );
@ -79,7 +79,7 @@ class CustomerHelper {
* @return array
*/
public static function get_expected_store_location() {
return array( 'GB', '', '', '' );
return array( 'US', 'CA', '', '' );
}
/**

View File

@ -70,9 +70,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@ -83,9 +83,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
@ -315,9 +315,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@ -328,9 +328,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,

View File

@ -77,9 +77,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@ -90,9 +90,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
@ -147,9 +147,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@ -160,9 +160,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
@ -387,9 +387,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@ -400,9 +400,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
'city' => 'Philadelphia',
'state' => 'PA',
'postcode' => '19123',
'city' => 'San Francisco',
'state' => 'CA',
'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,

View File

@ -117,8 +117,8 @@ class WC_Tests_Tax extends WC_Unit_Test_Case {
*/
public function test_get_base_tax_rates() {
$tax_rate = array(
'tax_rate_country' => 'GB',
'tax_rate_state' => '',
'tax_rate_country' => 'US',
'tax_rate_state' => 'CA',
'tax_rate' => '20.0000',
'tax_rate_name' => 'VAT',
'tax_rate_priority' => '1',

View File

@ -30,7 +30,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
*/
public function test_get_woocommerce_currency() {
$this->assertEquals( 'GBP', get_woocommerce_currency() );
$this->assertEquals( 'USD', get_woocommerce_currency() );
}
/**
@ -222,10 +222,10 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
public function test_get_woocommerce_currency_symbol() {
// Default currency.
$this->assertEquals( '&pound;', get_woocommerce_currency_symbol() );
$this->assertEquals( '&#36;', get_woocommerce_currency_symbol() );
// Given specific currency.
$this->assertEquals( '&#36;', get_woocommerce_currency_symbol( 'USD' ) );
$this->assertEquals( '&pound;', get_woocommerce_currency_symbol( 'GBP' ) );
// Each case.
foreach ( array_keys( get_woocommerce_currencies() ) as $currency_code ) {
@ -348,8 +348,8 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
public function test_wc_get_base_location() {
$default = wc_get_base_location();
$this->assertEquals( 'GB', $default['country'] );
$this->assertEquals( '', $default['state'] );
$this->assertEquals( 'US', $default['country'] );
$this->assertEquals( 'CA', $default['state'] );
}
/**

View File

@ -0,0 +1,59 @@
<?php
/**
* Tests for WC_Shop_Customizer
*/
use Automattic\WooCommerce\Internal\ThemeSupport;
require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
require_once ABSPATH . WPINC . '/class-wp-customize-control.php';
/**
* Tests for WC_Shop_Customizer
*/
class WC_Shop_Customizer_Test extends WC_Unit_Test_Case {
/**
* Runs before each test of the class.
*/
public function setUp() {
remove_theme_support( 'woocommerce' );
}
/**
* @testdox add_sections should add controls for image size settings only if these sizes haven't been explicitly declared as theme support (not counting default declarations).
*
* @testWith ["single_image_width", true, false]
* ["single_image_width", false, true]
* ["thumbnail_image_width", true, false]
* ["thumbnail_image_width", false, true]
*
* @param string $option_name The option name to test, either 'single_image_width' or 'thumbnail_image_width'.
* @param bool $add_explicit_theme_support True to test when theme support is added explicitly (not as a default value).
* @param bool $expected_to_have_added_customization True to expect the customization to have been added, false to expect it no to have been added.
*/
public function test_add_sections_should_add_image_controls_only_if_no_theme_support_for_image_sizes_is_defined( $option_name, $add_explicit_theme_support, $expected_to_have_added_customization ) {
$customize_manager = $this->createMock( WP_Customize_Manager::class );
$added_settings = array();
$added_controls = array();
$add_setting_callback = function( $id, $args = array() ) use ( &$added_settings ) {
array_push( $added_settings, $id );
};
$add_control_callback = function( $id, $args = array() ) use ( &$added_controls ) {
array_push( $added_controls, $id );
};
$customize_manager->method( 'add_setting' )->will( $this->returnCallback( $add_setting_callback ) );
$customize_manager->method( 'add_control' )->will( $this->returnCallback( $add_control_callback ) );
$theme_support = $this->get_instance_of( ThemeSupport::class );
$add_support_method = $add_explicit_theme_support ? 'add_options' : 'add_default_options';
$theme_support->$add_support_method( array( $option_name => 1234 ) );
$sut = $this->get_legacy_instance_of( WC_Shop_Customizer::class );
$sut->add_sections( $customize_manager );
$this->assertEquals( $expected_to_have_added_customization, in_array( 'woocommerce_' . $option_name, $added_settings, true ) );
$this->assertEquals( $expected_to_have_added_customization, in_array( 'woocommerce_' . $option_name, $added_controls, true ) );
}
}

View File

@ -192,6 +192,17 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
}
}
/**
* Assert that a value is equal to another one and is of integer type.
*
* @param mixed $expected The value $actual must be equal to.
* @param mixed $actual The value to check for equality to $expected and for type.
*/
private function assertIsIntAndEquals( $expected, $actual ) {
$this->assertEquals( $expected, $actual );
self::assertIsInteger( $actual );
}
/**
* Test wc_get_low_stock_amount with a simple product which has low stock amount set.
*/
@ -200,7 +211,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$site_wide_low_stock_amount = 3;
// Set the store-wide default.
update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Simple product, set low stock amount.
$product = WC_Helper_Product::create_simple_product(
@ -212,7 +223,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
)
);
$this->assertEquals( $product_low_stock_amount, wc_get_low_stock_amount( $product ) );
$this->assertIsIntAndEquals( $product_low_stock_amount, wc_get_low_stock_amount( $product ) );
}
/**
@ -222,18 +233,18 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$site_wide_low_stock_amount = 3;
// Set the store-wide default.
update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Simple product, don't set low stock amount.
$product = WC_Helper_Product::create_simple_product(
true,
array(
'manage_stock' => true,
'stock_quantity' => 10,
'manage_stock' => true,
'stock_quantity' => 10,
)
);
$this->assertEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $product ) );
$this->assertIsIntAndEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $product ) );
}
/**
@ -245,7 +256,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$variation_low_stock_amount = 7;
// Set the store-wide default.
update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Parent low stock amount NOT set.
$variable_product = WC_Helper_Product::create_variation_product();
@ -254,22 +265,22 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
$var1 = $variations[0];
$var1 = $variations[0];
$var1->set_manage_stock( true );
$var1->set_low_stock_amount( $variation_low_stock_amount );
$var1->save();
$this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
$this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
// Even after turning on manage stock on the parent, but with no value.
$variable_product->set_manage_stock( true );
$variable_product->save();
$this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
$this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
// Ans also after turning the manage stock off again on the parent.
$variable_product->set_manage_stock( false );
$variable_product->save();
$this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
$this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
/**
@ -282,7 +293,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$variation_low_stock_amount = 7;
// Set the store-wide default.
update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Set the parent low stock amount.
$variable_product = WC_Helper_Product::create_variation_product();
@ -292,12 +303,12 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
$var1 = $variations[0];
$var1 = $variations[0];
$var1->set_manage_stock( true );
$var1->set_low_stock_amount( $variation_low_stock_amount );
$var1->save();
$this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
$this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
/**
@ -309,7 +320,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$parent_low_stock_amount = 5;
// Set the store-wide default.
update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Set the parent low stock amount.
$variable_product = WC_Helper_Product::create_variation_product();
@ -319,9 +330,9 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Don't set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
$var1 = $variations[0];
$var1 = $variations[0];
$this->assertEquals( $parent_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
$this->assertIsIntAndEquals( $parent_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
/**
@ -332,7 +343,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$site_wide_low_stock_amount = 3;
// Set the store-wide default.
update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Set the parent low stock amount.
$variable_product = WC_Helper_Product::create_variation_product();
@ -340,10 +351,10 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Don't set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
$var1 = $variations[0];
$var1 = $variations[0];
$var1->set_manage_stock( false );
$this->assertEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
$this->assertIsIntAndEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* AssignDefaultCategoryTest class file.
*/
namespace Automattic\WooCommerce\Tests\Internal;
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper;
/**
* Tests for AssignDefaultCategory.
*/
class AssignDefaultCategoryTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var AssignDefaultCategory
*/
private $sut;
/**
* Test to make sure products without categories will be
* assigned a default category always.
*/
public function test_products_are_assigned_a_default_category() {
global $wpdb;
$this->sut = new AssignDefaultCategory();
$product1 = ProductHelper::create_simple_product();
$product2 = ProductHelper::create_simple_product();
$product3 = ProductHelper::create_simple_product();
$default_category = (int) get_option( 'default_product_cat', 0 );
$products = array( $product1, $product2, $product3 );
// Remove all categories from products.
foreach ( $products as $product ) {
$result = $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->term_relationships} WHERE object_id = %d AND term_taxonomy_id = %d",
$product->get_id(),
$default_category
)
);
}
// Ensure all categories are removed from products.
foreach ( $products as $product ) {
$cats = wp_get_post_terms( $product->get_id(), 'product_cat' );
$this->assertEmpty( $cats );
}
// Add in default category.
$this->sut->maybe_assign_default_product_cat();
// Ensure default category are now assigned to products.
foreach ( $products as $product ) {
$cats = wp_list_pluck( wp_get_post_terms( $product->get_id(), 'product_cat' ), 'term_id' );
$this->assertContains( $default_category, $cats );
}
}
}

View File

@ -0,0 +1,254 @@
<?php
/**
* Tests for ThemeSupport
*
* @package Automattic\WooCommerce\Tests\ThemeSupport
*/
namespace Automattic\WooCommerce\Tests\ThemeManagement;
use Automattic\WooCommerce\Internal\ThemeSupport;
/**
* Tests for ThemeSupport
*/
class ThemeSupportTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var ThemeSupport
*/
private $sut;
/**
* Runs before each test.
*/
public function setUp() {
$this->sut = $this->get_instance_of( ThemeSupport::class );
remove_theme_support( 'woocommerce' );
}
/**
* @testdox add_options should add the supplied options under the 'woocommerce' feature.
*/
public function test_add_options() {
$actual_added_feature = null;
$actual_added_options = null;
$this->register_legacy_proxy_function_mocks(
array(
'add_theme_support' => function( $feature, ...$args ) use ( &$actual_added_feature, &$actual_added_options ) {
$actual_added_feature = $feature;
$actual_added_options = $args;
},
)
);
$options = array( 'foo' => 'bar' );
$this->sut->add_options( $options );
$this->assertEquals( 'woocommerce', $actual_added_feature );
$this->assertEquals( $options, $actual_added_options[0] );
$this->reset_legacy_proxy_mocks();
$this->sut->add_options( $options );
$actual_retrieved_options = get_theme_support( 'woocommerce' )[0];
$this->assertEquals( $options, $actual_retrieved_options );
}
/**
* @testdox add_default_options should add the supplied options under the 'woocommerce' feature on a '_defaults' key.
*/
public function test_2_add_default_options() {
$actual_added_options = array();
$this->register_legacy_proxy_function_mocks(
array(
'add_theme_support' => function( $feature, ...$args ) use ( &$actual_added_options ) {
array_push( $actual_added_options, $args );
},
)
);
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
$this->sut->add_default_options( array( 'fizz' => 'buzz' ) );
$expected_added_options = array(
array(
array(
ThemeSupport::DEFAULTS_KEY =>
array(
'foo' => 'bar',
),
),
),
array(
array(
ThemeSupport::DEFAULTS_KEY =>
array(
'fizz' => 'buzz',
),
),
),
);
$this->assertEquals( $expected_added_options, $actual_added_options );
$this->reset_legacy_proxy_mocks();
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
$this->sut->add_default_options( array( 'fizz' => 'buzz' ) );
$actual_retrieved_options = get_theme_support( 'woocommerce' )[0];
$expected_retrieved_options = array(
ThemeSupport::DEFAULTS_KEY => array(
'foo' => 'bar',
'fizz' => 'buzz',
),
);
$this->assertEquals( $expected_retrieved_options, $actual_retrieved_options );
}
/**
* @testdox add_default_options should add the supplied options under the 'woocommerce' feature on a '_defaults' key.
*/
public function test_add_default_options() {
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
$this->sut->add_default_options( array( 'fizz' => 'buzz' ) );
$actual = get_theme_support( 'woocommerce' )[0];
$expected = array(
ThemeSupport::DEFAULTS_KEY => array(
'foo' => 'bar',
'fizz' => 'buzz',
),
);
$this->assertEquals( $expected, $actual );
}
/**
* @testdox get_option should return all the options under the 'woocommerce' feature when invoked with blank option name.
*/
public function test_get_option_with_no_option_name() {
$options = array( 'foo' => 'bar' );
$this->sut->add_options( $options );
$actual = $this->sut->get_option();
$this->assertEquals( $options, $actual );
}
/**
* @testdox get_option should return null if no 'woocommerce' feature exists and no default value is supplied.
*/
public function test_get_option_with_no_option_name_when_no_options_exist_and_no_default_value_supplied() {
$actual = $this->sut->get_option();
$this->assertNull( $actual );
}
/**
* @testdox get_option should return the supplied default value if no 'woocommerce' feature exists.
*/
public function test_get_option_with_no_option_name_when_no_options_exist_and_default_value_supplied() {
$actual = $this->sut->get_option( '', 'DEFAULT' );
$this->assertEquals( 'DEFAULT', $actual );
}
/**
* @testdox get_theme_support should return the value of the requested option if it exists.
*/
public function test_get_theme_support_with_option_name() {
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
$this->sut->add_options( $options );
$actual = $this->sut->get_option( 'foo::bar' );
$this->assertEquals( 'fizz', $actual );
}
/**
* @testdox get_option should return null if the requested option doesn't exist and no default value is supplied.
*/
public function test_get_option_with_option_name_when_option_does_not_exist_and_no_default_value_supplied() {
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
$this->sut->add_options( $options );
$actual = $this->sut->get_option( 'buzz' );
$this->assertNull( $actual );
}
/**
* @testdox get_option should return the supplied default value if the requested option doesn't exist.
*/
public function test_get_option_with_option_name_when_option_does_not_exist_and_default_value_supplied() {
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
$this->sut->add_options( $options );
$actual = $this->sut->get_option( 'buzz', 'DEFAULT' );
$this->assertEquals( 'DEFAULT', $actual );
}
/**
* @testdox get_option should return the value of the requested option if it has been defined as a default.
*/
public function test_get_option_with_option_name_and_option_defined_as_default() {
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
$this->sut->add_default_options( $options );
$actual = $this->sut->get_option( 'foo::bar' );
$this->assertEquals( 'fizz', $actual );
}
/**
* @testdox has_option should return false if no 'woocommerce' feature exists.
*
* @testWith [true]
* [false]
*
* @param bool $include_defaults Whether to include defaults in the search or not.
*/
public function test_has_option_when_no_woocommerce_feature_is_defined( $include_defaults ) {
$this->assertFalse( $this->sut->has_option( 'foo::bar', $include_defaults ) );
}
/**
* @testdox has_option should return false if the specified option has not been defined.
*
* @testWith [true]
* [false]
*
* @param bool $include_defaults Whether to include defaults in the search or not.
*/
public function test_has_option_when_option_is_not_defined( $include_defaults ) {
$this->sut->add_options( array( 'foo' => 'bar' ) );
$this->assertFalse( $this->sut->has_option( 'fizz::buzz', $include_defaults ) );
}
/**
* @testdox has_option should return true if the specified option has been defined.
*
* @testWith [true]
* [false]
*
* @param bool $include_defaults Whether to include defaults in the search or not.
*/
public function test_has_option_when_option_is_defined( $include_defaults ) {
$this->sut->add_options( array( 'foo' => 'bar' ) );
$this->assertTrue( $this->sut->has_option( 'foo', $include_defaults ) );
}
/**
* @testdox If an option has been defined as a default, has_theme_support should return true if $include_defaults is passed as true, should return false otherwise.
*
* @testWith [true, true]
* [false, false]
*
* @param bool $include_defaults Whether to include defaults in the search or not.
* @param bool $expected_result The expected return value from the tested method.
*/
public function test_has_option_when_option_is_defined_as_default( $include_defaults, $expected_result ) {
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
$this->assertEquals( $expected_result, $this->sut->has_option( 'foo', $include_defaults ) );
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Automattic\WooCommerce\Tests\Utilities;
use Automattic\WooCommerce\Utilities\ArrayUtil;
/**
* A collection of tests for the array utility class.
*/
class ArrayUtilTest extends \WC_Unit_Test_Case {
/**
* @testdox `get_nested_value` should return null if the requested key doesn't exist and no default value is supplied.
*
* @testWith ["foo"]
* ["foo::bar"]
*
* @param string $key The key to test.
*/
public function test_get_nested_value_returns_null_if_non_existing_key_and_default_not_supplied( $key ) {
$array = array( 'fizz' => 'buzz' );
$actual = ArrayUtil::get_nested_value( $array, $key );
$this->assertNull( $actual );
}
/**
* @testdox `get_nested_value` should return the supplied default value if the requested key doesn't exist.
*
* @testWith ["foo"]
* ["foo::bar"]
*
* @param string $key The key to test.
*/
public function test_get_nested_value_returns_supplied_default_if_non_existing_key( $key ) {
$array = array( 'fizz' => 'buzz' );
$actual = ArrayUtil::get_nested_value( $array, $key, 'DEFAULT' );
$this->assertEquals( 'DEFAULT', $actual );
}
/**
* @testdox `get_nested_value` should return the proper value when a simple key is passed.
*/
public function test_get_nested_value_works_for_simple_keys() {
$array = array( 'foo' => 'bar' );
$actual = ArrayUtil::get_nested_value( $array, 'foo' );
$this->assertEquals( 'bar', $actual );
}
/**
* @testdox `get_nested_value` should return the proper value when a nested key is passed.
*/
public function test_get_nested_value_works_for_nested_keys() {
$array = array(
'foo' => array(
'bar' => array(
'fizz' => 'buzz',
),
),
);
$actual = ArrayUtil::get_nested_value( $array, 'foo::bar::fizz' );
$this->assertEquals( 'buzz', $actual );
}
}

View File

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce
* Plugin URI: https://woocommerce.com/
* Description: An eCommerce toolkit that helps you sell anything. Beautifully.
* Version: 5.3.0-dev
* Version: 5.4.0-dev
* Author: Automattic
* Author URI: https://woocommerce.com
* Text Domain: woocommerce